Wednesday, February 22, 2017

Porting OpenWRT to ionik Wifi Cloud Hub

I will probably have to split this article into several parts, as I move along.

In the initial article (see http://hackcorrelation.blogspot.de/2017/01/ionik-cloud-hub.html) I took a look at the hardware and started doing some basic hacking. However, I quickly ran into limitations and decided to try and port OpenWRT for the platform.

There are quite a few steps and preparations needed to do this, however, this is for documentary purposes only. You don't need to do any of the stuff below (except backup), you can just flash my OpenWRT firmware and be none the wiser. However it might be useful if you want to port it to a new device, I could not find any tutorial about this.

Step 0 - BACKUP


In case everything blows up you will want to be able to restore everything to the factory condition. I first reset the root password to 'admin' by going to this link:
http://10.10.10.254:8000/goform/SystemCommand?command=%2Fsbin%2Fchpasswd.sh+root+admin&SystemCommandSubmit=Apply

This is not necessary now since some nice people have decoded the factory root password: 91657853.
Connect to the serial pads, log in as root.

I liked to have a Samba share so I could copy the entire file system. So type this inside the shell:

printf '\n[rootFS]\npath=/\nvalid users = admin\nbrowseable = yes\nwritable = yes\n' >>/etc/smb.conf

This creates a Windows share that can be accessed with admin/admin. Copy everything except /dev, /media and /sys to some local storage:
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----         1/1/1980  12:00 AM                bin
d-----         1/1/1980  12:00 AM                etc
d-----         1/1/1980  12:00 AM                etc_ro
d-----         1/1/1980  12:00 AM                home
d-----         1/1/1980  12:00 AM                lib
d-----         1/1/1980  12:00 AM                sbin
d-----         1/1/1980  12:00 AM                tmp
d-----         1/1/1980  12:00 AM                usr
d-----         1/1/1980  12:00 AM                var
With a USB stick and an SD card inserted we can type 'mount' to see where these get mounted.

rootfs on / type rootfs (rw)
proc on /proc type proc (rw,relatime)
none on /var type ramfs (rw,relatime)
none on /etc type ramfs (rw,relatime)
none on /tmp type ramfs (rw,relatime)
none on /media type ramfs (rw,relatime)
none on /sys type sysfs (rw,relatime)
none on /dev/pts type devpts (rw,relatime,mode=600)
none on /proc/bus/usb type usbfs (rw,relatime)
mdev on /dev type ramfs (rw,relatime)
devpts on /dev/pts type devpts (rw,relatime,mode=600)
/dev/sdb1 on /media/UBUNTU_16_1 type vfat (rw,relatime,fmask=0000,dmask=0000,allow_utime=0022,codepage=cp950,iocharset=utf8,shortname=mixed,utf8,errors=remount-ro)
/dev/sda2 on /media/OS_X_Base_System type hfsplus (rw,relatime,umask=0,uid=0,gid=0,nls=utf8)

It seems sdb corresponds to the USB stick and sda to the SD card. There is an EFI partition (sda1) which gets ignored.
We need to do a block dump of the entire flash contents.

# dd if=/dev/mtdblock0 of=/media/UBUNTU_16_1/mtdblock0
16384+0 records in
16384+0 records out
# dd if=/dev/mtdblock1 of=/media/UBUNTU_16_1/mtdblock1
384+0 records in
384+0 records out
# dd if=/dev/mtdblock2 of=/media/UBUNTU_16_1/mtdblock2
128+0 records in
128+0 records out
# dd if=/dev/mtdblock3 of=/media/UBUNTU_16_1/mtdblock3
128+0 records in
128+0 records out
# dd if=/dev/mtdblock4 of=/media/UBUNTU_16_1/mtdblock4
3584+0 records in
3584+0 records out
# dd if=/dev/mtdblock5 of=/media/UBUNTU_16_1/mtdblock5
12160+0 records in
12160+0 records out

Now the flash contents are stored safely on our USB stick.
We could also copy the file system to the USB stick instead of using a network share: cp -RLf /etc_ro /media/UBUNTU_16_1/ionik/etc_ro

Entry points


We need to identify how the flash is partitioned:

# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00800000 00010000 "ALL"
mtd1: 00030000 00010000 "Bootloader"
mtd2: 00010000 00010000 "Config"
mtd3: 00010000 00010000 "Factory"
mtd4: 001c0000 00010000 "MiniSystem"
mtd5: 005f0000 00010000 "Kernel"

Description:
  • mtd0: 0x00000-0x800000 entire flash contents
  • mtd1: 0x00000-0x30000 is the uBoot bootloader
  • mtd2: 0x30000-0x40000 stores the user-defined configuration
  • mtd3: 0x40000-0x50000 stores some internal configuration (WiFi registers?)
  • mtd4: 0x50000-0x210000 is the failsafe (recovery) firmware
  • mtd5: 0x21000-0x800000 is the normal firmware

How booting works

When the CPU starts up it goes to the first address inside the flash memory to look for an executable. It finds uBoot at position 0x00 and hands over control to that. uBoot then does some housekeeping and decides where to jump next.
In the case of this device, if the reset button is kept pressed while powering on, uBoot will jump to 0x50000 (mtd4), otherwise it will jump to 0x21000 (mtd5).

Example:
Normal boot
3: System Boot system code via Flash.
## Booting image at bc210000 ...
raspi_read: from:210000 len:40
.   Image Name:   Linux Kernel Image
   Created:      2013-09-09   8:53:17 UTC
   Image Type:   MIPS Linux Kernel Image (lzma compressed)
   Data Size:    6025942 Bytes =  5.7 MB
   Load Address: 80000000
   Entry Point:  8000c310
raspi_read: from:210040 len:5bf2d6
 Failsafe (minisystem) boot:
7: System Boot mini system code via Flash.
## Booting image at bc050000 ...
raspi_read: from:50000 len:40
.   Image Name:   Linux Kernel Image
   Created:      2013-03-11   6:20:33 UTC
   Image Type:   MIPS Linux Kernel Image (lzma compressed)
   Data Size:    1649876 Bytes =  1.6 MB
   Load Address: 80000000
   Entry Point:  8000c310
raspi_read: from:50040 len:192cd4

I don't know much else to tell you, this is all pretty new to me as well. Think of uBoot as "grub" on your computer.

LED & GPIO


It would be nice to be able to read and write to GPIO pins. I haven't made much progress here, or perhaps the system is limited in this regard. Here are my notes:

blue power on, not charging (yellow off), blue activity off(?), reset off # gpio r
gpio 27~22 = 0x28   ->            10 1000
gpio 21~00 = 0x7c81 -> 111 1100 1000 0001
above + led yellow (charge on):
gpio 27~22 = 0x28
gpio 21~00 = 0x7c81
reset pressed:
gpio 27~22 = 0x28   ->            10 1000
gpio 21~00 = 0x7881 -> 111 1000 1000 0001
failsafe mode, magenta(?) led on, yellow on, blue off
gpio 27~22 = 0x28
gpio 21~00 = 0x7c81
red led blinking # gpio l 9 1 1 10 1 5
led=9, on=1, off=1, blinks,=10, reset=1, time=5

Conclusion: wifi led (blue), power led (blue), charge led (yellow) seem to be hardwired, Red led is bit 9+1 of GPIO; reset is pin 10+1
There's also a low battery indicator that I was not able to read or write to.

Getting dirty - OpenWRT configuration


The first step is to retrieve the repository, set up all the tools and dependencies. I will not go into those details as they are a moving target.

We already know from the teardown that the CPU is Ralink RT5350F. Luckily, OpenWRT already provides this platform for us under the name rt305x.

Add the following entry into /target/linux/ramips/base-files/lib/ramips.sh :

+ *"i.onik Wi-Fi Cloud Hub")
+ name="ionik-cloud-hub"
+ ;;

Then this under /target/linux/ramips/base-files/lib/upgrade/platform.sh, under the ip2202 entry :

+ ionik-cloud-hub|\

Not sure how correct is that. I think this file controls how the original (factory) firmware does checksumming in order to get your "trojan" firmware accepted as an upgrade.

Then this under /target/linux/ramips/image/rt305x.mk :

+Image/Build/Profile/IONIKCLOUDHUB=$(call BuildFirmware/Default8M/$(1),$(1),ionik-cloud-hub,IONIKCLOUDHUB,Linux Kernel Image) 

....and a bit lower inside the file...

+ $(call Image/Build/Profile/IONIKCLOUDHUB,$(1))

I found out that - after a test build - my custom firmware only recognized 32M of RAM instead of the 64M that are available. So modify the CONFIG_CMDLINE parameter inside target/linux/ramips/rt305x/config-4.4 b/target/linux/ramips/rt305x/config-4.4 :

+CONFIG_CMDLINE="rootfstype=squashfs,jffs2 mem=64M" 

Specifying the network interfaces, I probably goofed on this but it still works, add the platform inside target/linux/ramips/base-files/etc/board.d/02_network :
@@ -142,6 +142,7 @@ ramips_setup_interfaces()
  "0:lan" "1:wan" "6@eth0"
  ;;
  b2c|\
+ ionik-cloud-hub|\
  nw718|\
  psr-680w|\
  sl-r7205|\ 

I wanted to flash the red LED when booting but it did not work. target/linux/ramips/base-files/etc/diag.sh :
@@ -138,6 +138,7 @@ get_status_led() {
  status_led="$board:blue:status"
  ;;
  miwifi-mini|\
+ ionik-cloud-hub|\
  zte-q7)
  status_led="$board:red:status"
  ;;

I also want the wireless to be up after booting since the 'router' lacks an ethernet port. This could be done in a better way, with a default password or one based on MAC. So from package/kernel/mac80211/files/lib/wifi/mac80211.sh  you need to comment out the line that says 'option disabled 1'.

Then we need to add a file inside target/linux/ramips/rt305x/profiles called ionikcloudhub.mk.
...
define Profile/IONIKCLOUDHUB
NAME:=IONIK CLOUD HUB
PACKAGES:=\
kmod-ledtrig-netdev kmod-ledtrig-timer kmod-leds-gpio \
kmod-usb-core kmod-usb-ohci kmod-usb2 kmod-usb-net usbutils \
kmod-scsi-core kmod-scsi-generic kmod-fs-ext4 kmod-fs-msdos \
kmod-usb-storage kmod-usb-storage-extras block-mount
endef

define Profile/IONIKCLOUDHUB/Description
Package set for i.onik Wi-Fi Cloud Hub
endef
$(eval $(call Profile,IONIKCLOUDHUB))
...

Getting dirtier - DTS


This was the most painful part for me. To start off, create a file called IONIKCLOUDHUB.dts inside target/linux/ramips/dts.

I started by looking at routers with similar features and copied that file. I think I used some Western Digital WiFi HDD or something.

The DTS files describes the devices available for the Linux subsystem. The most important part however is the flash configuration.

Let's go into a quick walkthrough, I won't pretend to know what everything does:

/dts-v1/;

#include "rt5350.dtsi"

/ {
compatible = "IONIKCLOUDHUB", "ralink,rt5350-soc";
model = "i.onik Wi-Fi Cloud Hub";

Pretty easy, define the product name and compatible platform, include some ready-made devices inside rt5350.dtsi.

gpio-leds {
compatible = "gpio-leds";

status {
label = "ionikcloudhub:red:status";
gpios = <&gpio0 9 1>;
};
};

gpio-keys-polled {
compatible = "gpio-keys-polled";
#address-cells = <1>;
#size-cells = <0>;
poll-interval = <20>;

power {
label = "power";
gpios = <&gpio0 0 1>;
linux,code = <0x116>;
};

reset {
label = "reset";
gpios = <&gpio0 10 1>;
linux,code = <0x198>;
};
};
};

My attempt at specifying the GPIO devices.

&spi0 {
status = "okay";

en25q64@0 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "jedec,spi-nor";
reg = <0>;
linux,modalias = "m25p80", "en25q64";
spi-max-frequency = <10000000>;

partition@0 {
label = "u-boot";
reg = <0x0 0x30000>;
read-only;
};

partition@30000 {
label = "u-boot-env";
reg = <0x30000 0x10000>;
read-only;
};

factory: partition@40000 {
label = "factory";
reg = <0x40000 0x10000>;
read-only;
};

partition@50000 {
label = "recover";
reg = <0x50000 0x1c0000>;
};

partition@210040 {
label = "firmware";
reg = <0x210000 0x5f0000>;
};
};
};

This is the most important part. It specifies the flash configuration, for example it tells that uBoot resides between addresses 0x0 and 0x30000. I don't know if this is correct, but it works for me. The important bit is the "firmware" partition at the end. Everything else was mostly guesswork and might be wrong. But it works.

&pinctrl {
state_default: pinctrl0 {
gpio {
ralink,group = "i2c", "jtag", "rgmii", "mdio", "uartf";
ralink,function = "gpio";
};
};
};

&ehci {
status = "okay";
};

&ohci {
status = "okay";
};

&wmac {
ralink,mtd-eeprom = <&factory 0>;
};

I have no idea what anything above does. But every other device seems to have them, so I added them in. EHCI and OHCI refer to USB ports, as far as I can tell.

Building the custom firmware


After making sure all the files above are modified/added, you just need to type "make menuconfig".
Then go wild adding options to your ROM. You will quickly run out of flash space.

An option marked as 'm' means module. The option is compiled but not included inside the image. Rather, you can add it later to your device, temporarily, via a USB stick or similar. It might trigger (on) some other features which will eat precious Flash space. So just add only what you need, check the size of the image, repeat.

To build run "make -j4". This builds the firmware using 4 threads. If anything fails, you will have to build single-threaded and with verbose mode one. Refer to the documentation.

The resulting image will be in the bin/rampis folder:

adminuser@virtualboximagescom-VirtualBox-14:~/openwrt/bin/ramips$ ls -l
total 46132
-rw-r--r--  1       740 Jan 20 16:45 md5sums
-rw-r--r--  1  5812101 Jan 20 16:45 openwrt-ramips-rt305x-ionik-cloud-hub-initramfs-uImage.bin
-rw-r--r--  1 6291460 Jan 20 16:45 openwrt-ramips-rt305x-ionik-cloud-hub-squashfs-sysupgrade.bin
-rw-r--r--  1 5111808 Jan 20 16:45 openwrt-ramips-rt305x-root.squashfs
-rw-r--r--  1 1129526 Jan 20 16:45 openwrt-ramips-rt305x-uImage.bin
-rw-r--r--  1  5810315 Jan 20 16:45 openwrt-ramips-rt305x-uImage-initramfs.bin
-rwxr-xr-x  1 3452468 Jan 20 16:45 openwrt-ramips-rt305x-vmlinux.bin
-rwxr-xr-x  1 3457480 Jan 20 16:45 openwrt-ramips-rt305x-vmlinux.elf
-rwxr-xr-x  1 8071108 Jan 20 16:45 openwrt-ramips-rt305x-vmlinux-initramfs.bin
-rwxr-xr-x  1 8076120 Jan 20 16:45 openwrt-ramips-rt305x-vmlinux-initramfs.elf
drwxr-xr-x 10     4096 Jan 11 22:36 packages
-rw-r--r--  1 1140 Jan 20 16:45 sha256sums

The uImage-initramfs file is the one you need to be looking at. It has to stay under 0x5F0000 bytes (6,225,920 bytes for those stuck in decimal).
But it should not get close to that value, as the remainder of the space is used by OpenWRT to keep user configuration. I recommend to stay under 6,100,000 bytes.
If your image exceeds that size you need to run 'make menuconfig' and play with the options until you get it to fit.


Flashing your first image


If inside the normal (factory) firmware:


# mtd_write write /media/sda1/mtdblock5 Kernel
Unlocking Kernel ...
The system is going down NOW!lock5 to Kernel ...  [w]
Sending SIGTERM to all processes
Terminated
Requesting system reboot
Restarting system.

Replace the mtdblock5 file with the *squashfs-sysupgrade.bin from above.

Inside the failsafe firmware you can run this:

# mtd_write -r write /media/sda1/openwrt-ramips-rt305x-ionik-cloud-hub-squashfs-sysupgrade.bin mtd5
Unlocking mtd5 ...
The system is goingRestarting system.ps-rt305x-ionik-cloud-hub-squashfs-sysupgrade.bin to mtd5 ...  [w]

I don't remember the details right now, the above might be reversed, but you can play around if you have a backup. Make sure you are writing to the correct partition and do not touch uBoot and minisystem.

If you mess up, you will have to use an SPI flasher to restore the flash contents. There are various tools and tutorials online, you could probably use a Raspberry PI or Arduino for this, but you would still have to solder the tiny wires or buy a SOIC-8 clip.


After the first OpenWRT image is flashed from the command line, and it works, subsequent images can be uploaded from the web interface.

Update - Pastebin link with the source changes http://pastebin.com/xQs4qXri

Caveats


The build configuration files might be wrong.

Related to the above, the name of the device does not follow any convention. It might also require renaming to make an OpenWRT upgrade possible by uploading from the original firmware's web interface.

I haven't found a way to remove IPv6, it seems to be a bug in the OpenWRT build tools.

I don't know what I'm doing and this is not helped by the fact that I could not get any community support, at least not on the openwrt forums.

I will upload the configuration files and resulting firmware at some point in the near future, remind me of that if you need it before then.

Sunday, February 12, 2017

Designing a better diesel tuning box - part 5 - tweaks and updates

Living close to a densely populated area means that it's hard to do consistent testing without spending a lot of fuel, which is also expensive.

Nevertheless, I took the pen&paper approach and started working my way from the basics.

I studied all the Bosch sensors from this page, used a bit of common sense and figured out that my sensor is a Bosch 0281002691, or similar, with a 180 MPa (26000 psi) nominal rating. This might be wrong but It's a good place to start.

I've already used Torque and my multimeter to get some data and it seems to fit with the sensor I've chosen. It might be wrong, but so far it clicks into place.

Using the data I've gathered I've created a simple JS page that shows some logs and tries to simulate what my module (and sensor) does: https://jsbin.com/goyavabuju/edit?html,output
(Note that this is the HTML/JS result after I did the interpolation.)



So the basic function is: the ECU commands the pump, this delivers a pressure, my module receives the sensor data (voltage), offsets it, outputs a new voltage to the ECU, the ECU processes it. I missed a step the last time: the ECU will receive the adjusted pressure, output a new pressure, the pump delivers, a new pressure is measured. This can cause oscillations within the engine, as the tuning module attempts to adjust for the new value.

Thursday, February 2, 2017

Automated coffee machine troubleshooting chart

My old post on the Saeco Talea Giro machine teardown (including deprecated one) has had tremendous success and I frequently get questions on how to fix this or that.

I am no specialist on this stuff but I've managed to keep my unit running after it was used in an office environment (>50000 coffees). So for me it's mostly guesswork and some logic, but I'll display this so that you can help figure out the problem with your own unit. I'll try to make this accessible to non-technical people, let me know if some idioms are too advanced.

I will try to update this guide with usual questions, but this is not a replacement for professional servicing.


Before troubleshooting make sure that the unit is cleaned and descaled and has enough water. Use the manual for this, each unit is different. Also, try turn the unit off and on, perhaps leaving it 1h undisturbed. This works around some of the bugs in software (firmware).
Learn the sounds of the machine and try to understand what it does in its normal state. There are several motors and they are easy to identify by sound.

Refer to my original post (from years ago) if you want to understand more about how such a unit is constructed: http://hackcorrelation.blogspot.de/2013/07/saeco-talea-automatic-coffee-machine.html