Install Debian on SolidRun Clearfog board

Monday, 15 June 2020
Écrit par
Grégory Soutadé

ClearFog base board

For a project, I need to run java on an ARMv7 core. I could have used my Cubox server, but I don't have enough memory to run web services & java in parallel. Fortunately, I could find a SolidRun ClearFog Base board. This board is mainly network focused and only have an old dual Cortex A9 with 1GB of memory which is quick reached by today web applications, but it should be okay with a single one (I hope).

SolidRun provides documentation & ready to use OS (Debian FreeBSD, Yocto, OpenWRT), so board start should take less than one half hour. Nevertheless, documentation is split in multiple files, and there is no step by step guide. Moreover, Debian images seems crafted (we can't read main ext4 partition). After 2 hours of fails I started to turn crazy ! Here is my step by step tutorial.

The goal is to install Debian on an 8GB SDCard

1) Get an 8GB SDCard and delete all partitions thanks to gparted (or fdisk).

2) Within gparted create an ext4 partition that starts at 4MB with 8GB length

3) Download target Debian images from SolidRun resources. You should take .tar.xz file which is a compressed tar file of root filesystem.

4) Go to SDCard main partition and extract data from Debian image (using sudo)

cd /media/soutade/UUID/
sudo tar -Jxvf ~/clearfog/sr-a38x-debian-buster-20200218.tar.xz

5) Update fstab with your new SDCard UUID

sudo emacs etc/fstab

6) Download U-Boot for base-sdhc variant

7) Burn it to SDCard at address 512 (first sector). We assume SDCard is /dev/sdb (check it before applying this command)

sudo dd if=u-boot-clearfog-base-sdhc.kwb of=/dev/sdb1 seek=1 bs=512

8) Configure ClearFog switches to start using SDcard (Off Off On On On)

9) Insert SDCard into the board, connect serial line USB to your computer

10) Power up the board and start serial line screen

sudo screen -L /dev/ttyUSB0 115200

11) Stop U-Boot process by hit a key

12) Setup kernel bootargs. Here we setup root filesystem read/write at startup, but it should be re mounted rw by fstab. Only root argument is mandatory

setenv bootargs root=/dev/mmcblk0p1 rw
env save

The board should now start with a fresh Debian ! I suggest to connect within SSH which is more comfortable to use than serial line console. Login/password is debian:debian.

Have fun !

Upgrade Cubox-i Linux kernel

Saturday, 05 October 2019
Écrit par
Grégory Soutadé

In the beginning of October, Debian pushed a security update for libssl. After installing it, all new SSH connections fails with message (even with correct password, or root login) :

fatal: privsep_preauth: preauth child terminated by signal 31

After searching on Internet, I found that nor SSH, nor libssl were in cause. It was due to an old kernel. I was running Linux 3.14 kernel because is not maintained.

Fortunately, Solid Run still maintains Linux kernel source tree on Github. Next instructions are based on this page.

First, mount Cubox-i filesystem from SDcard (assume it's in /mnt/cubox).

At startup, uBoot is configured to load zImage and dtb/$dtb_file. zImage is a symbolic link allowing us to have multiple kernel in /boot, let's do the same for dtb directory :

cd /mnt/cubox
cd boot
sudo mv dtb/ 3.4.14
sudo mkdir dtbs
sudo mv 3.4.14 dtbs
sudo ln -s dtbs/3.4.14/ dtb

Next, kernel compilation. The linked page suggest to do a git clone which is very big (~3GB), I suggest to download a snapshot from Github. Now, we'll follows Solid Run instructions :

sudo apt install crossbuild-essential-armhf
cd linux_sources
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
make imx_v7_cbi_hb_defconfig
make -j4 zImage dtbs modules

Then, install compiled files :

export INSTALL_PATH=$PWD/linux_install
export INSTALL_MOD_PATH=$PWD/modules_install
mkdir linux_install
make install modules_install dtbs_install
cp arch/arm/boot/zImage linux_install/vmlinuz-4.9.124
sudo cp -r linux_install/* /mnt/cubox/boot/
sudo cp -r modules_install/lib/modules/4.9.124/ /mnt/cubox/lib/modules/

Linux creates an image compressed with lzop which not seems to be supported by my version of uBoot, so we need to manually copy created zImage.

Modules installation can be done in one line :

sudo make modules_install INSTALL_MOD_PATH=/mnt/cubox/

Optionally, you can export headers :

sudo make headers_install INSTALL_HDR_PATH=/mnt/cubox/usr/local/include

Switch kernel

cd /mnt/cubox
sudo rm dtb
sudo ln -s dtbs/4.9.124/ dtb
sudo rm zImage
sudo ln -s vmlinuz-4.9.124 zImage

Unmount and unplug SDcard. Power up. It should now run new Linux kernel !

Solid Run also have a repository for a Debian package for kernel, but for now I didn't saw any binary repository available on Internet.

Warning, Github kernel make my server crash a lot of time due to an error in ext4/fs driver. I compiled a vanilla kernel, from linux-4.19.y branch (same as Debian stable one). Use the same instructions for compilation (just add dtbs_install to make install command). My .config is available here. I didn't test HDMI, Bluetooth nor IR (red LED is off). Last thing : root partition is now on /dev/mmcblk1p1, don't forget to update kernel command line !

Enabling serial console

The serial console seems to not work anymore. To enable it, first edit /etc/inittab and add at then end :

1:2345:respawn:/sbin/getty -L ttymxc0 115200 vt100

Then, we need to enable getty with SystemD to have login prompt at startup :

sudo systemctl enable "getty@ttymxc0"

Finally, we need to update kernel command line. Edit /boot/boot.cmd and put :


Build boot.scr from boot.cmd, documentation here:

mkimage -C none -A arm -T script -d boot.cmd boot.scr

Max stack usage for C program

Wednesday, 10 April 2019
Écrit par
Grégory Soutadé

Another day, another script. This one helps to compute the maximum stack usage of a C program. In facts, it combines the output of cflow and GCC GNAT to find the heaviest path used (which is not necessary the deepest). The first one compute target software call graph while option -fstack-usage of GCC creates .su files containing stack usage of all functions.

Targets software are simple embedded software. This script is a simple base not intended to run on all cases, handle dynamic stack nor recursive functions (if you wish to add it...).

A file version is available here.

#!/usr/bin/env python

import os
import re
import argparse

class SUInfo:
    def __init__(self, filename, line, func_name, stack_size):
        self.filename = filename
        self.line = line
        self.func_name = func_name
        self.stack_size = stack_size

    def __str__(self):
        s = '%s() <%s:%s> %d' % (self.func_name, self.filename, self.line, self.stack_size)
        return s

class FlowElement:
    def __init__(self, root, depth, stack_size, suinfo):
        self.root = root
        self.depth = depth
        self.stack_size = stack_size
        self.suinfo = suinfo
        self.childs = []

    def append(self, suinfo):

    def __str__(self):
        spaces = '    ' * self.depth
        su = self.suinfo
        res = '%s-> %s() %d <%s:%d>' % (spaces, su.func_name, su.stack_size,
                                        su.filename, su.line)
        return res

def display_max_path(element):
    print('Max stack size %d' % (element.stack_size))
    print('Max path :')
    res = ''
    while element:
        res = str(element) + '\n' + res
        element = element.root

cflow_re = re.compile(r'([ ]*).*\(\) \<.* at (.*)\>[:]?')

def parse_cflow_file(path, su_dict):
    root = None
    cur_root = None
    current = None
    cur_depth = 0
    max_stack_size = 0
    max_path = None
    with open(path) as f:
        while True:
            line = f.readline()
            if not line: break
            match = cflow_re.match(line)
            if not match: continue

            spaces =
            # Convert tab into 4 spaces
            spaces = spaces.replace('\t', '    ')
            depth = len(spaces)/4
            filename =
            (filename, line) = filename.split(':')
            filename = '%s:%s' % (os.path.basename(filename), line)

            suinfo = su_dict.get(filename, None)
            # Some functions may have been inlined
            if not suinfo:
                # print('WARNING: Key %s not found in su dict"' % (filename))

            if not root:
                root = FlowElement(None, 0, suinfo.stack_size, suinfo)
                cur_root = root
                current = root
                max_path = root
                max_stack_size = suinfo.stack_size
                # Go back
                if depth < cur_depth:
                    while cur_root.depth > (depth-1):
                        cur_root = cur_root.root
                # Go depth
                elif depth > cur_depth:
                    cur_root = current
                cur_depth = depth
                stack_size = cur_root.stack_size + suinfo.stack_size
                element = FlowElement(cur_root, cur_depth,
                current = element
                if stack_size > max_stack_size:
                    max_stack_size = stack_size
                    max_path = current

su_re = re.compile(r'(.*)\t([0-9]+)\t(.*)')

def parse_su_files(path, su_dict):
    for root, dirs, files in os.walk(path):
        for sufile in files:
            if sufile[-2:] != 'su': continue
            with open(os.path.join(path, sufile)) as f:
                while True:
                    line = f.readline()
                    if not line: break
                    match = su_re.match(line)
                    if not match:
                        # print('WARNING no match for "%s"' % (line))
                    infos =
                    (filename, line, size, function) = infos.split(':')
                    stack_size = int(
                    key = '%s:%s' % (filename, line)
                    su_info = SUInfo(filename, int(line), function, stack_size)
                    su_dict[key] = su_info

if __name__ == '__main__':
    optparser = argparse.ArgumentParser(description='Max static stack size computer')
    optparser.add_argument('-f', '--cflow-file', dest='cflow_file',
                           help='cflow generated file')
    optparser.add_argument('-d', '--su-dir', dest='su_dir',
                           help='Directory where GNAT .su files are generated')
    options = optparser.parse_args()

    su_dict = {}

    parse_su_files(options.su_dir, su_dict)
    parse_cflow_file(options.cflow_file, su_dict)

Usage & example

Let's take this simple software as example.

First, compile your software using -fstack-usage options in CFLAGS. It will creates an .su file for each object file. Then, launch cflow on your software. Finally, call my script.

mkdir test
cd test
gcc -fstack-usage gget.c -lpthread -lcurl
cflow gget.c > cflow.res
./ -f cflow.res


Max stack size 608
Max path :
-> main() 352 <gget.c:493>
    -> do_transfert() 160 <gget.c:228>
        -> progress_cb() 96 <gget.c:214>

Let's encrypt certificate renewal with Gandi LiveDNS API

Tuesday, 02 April 2019
Écrit par
Grégory Soutadé

It's now one year I use Let's Encrypt TLS wildcard certificates. Until now, all was fine, but since the beginning of 2019, there is two domains on my certificate : and * and (maybe due to my certificate generation) I need to perform two challenges for renewal : HTTP (http01) and DNS (dns01).

So, I wrote a Python script that performs both :

#!/usr/bin/env python3
#-*- encoding: utf-8 -*-

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <>.

# Handle certificate renewal using HTTP and DNS challenges
# DNS challenge performed by Gandi Live v5 API

import requests
import os
import argparse
import shutil

# Config
ACME_RECORD = '_acme-challenge'
ACME_CHALLENGE_PATH = '/var/www/.well-known/acme-challenge'

headers = {
    'X-Api-Key': API_KEY,

CERTBOT_TOKEN = os.environ.get('CERTBOT_TOKEN', None)
DOMAIN = os.environ.get('CERTBOT_DOMAIN', None)

optparser = argparse.ArgumentParser(description='Letsencrypt challenge for Gandi v5 API')
optparser.add_argument('-c', '--cleanup', dest='cleanup',
                       action="store_true", default=False,
                       help='Cleanup chanllenge')

options = optparser.parse_args()     

if options.cleanup:
    if os.path.exists(ACME_CHALLENGE_PATH):
        print('Build HTTP authentication')
        # Create token file for web server
        if not os.path.exists(ACME_CHALLENGE_PATH):
        token_path = os.path.join(ACME_CHALLENGE_PATH, CERTBOT_TOKEN)

        with open(token_path, 'w') as token:

response = requests.get(LIVEDNS_API + "zones", headers=headers)

target_zone = None
if (response.ok):
    zones = response.json()
    for zone in zones:
        if zone['name'] == DOMAIN:
            target_zone = zone

if not target_zone:
    print('Any zone found for domain %s' % (DOMAIN))

domain_records_href = target_zone['zone_records_href']

# Get TXT record
response = requests.get(domain_records_href + "/" + ACME_RECORD, headers=headers)

# Delete record if it exists
if (response.ok):
    requests.delete(domain_records_href + "/" + ACME_RECORD, headers=headers)

if options.cleanup:

print('Build DNS authentication')
record = {
    "rrset_name": ACME_RECORD,
    "rrset_type": "TXT",
    "rrset_ttl": 300,
    "rrset_values": [CERTBOT_VALIDATION],

response =,
                         headers=headers, json=record)

if (response.ok):
    print("DNS token created")
    print("Something went wrong")

A downloadable version is available here


In /etc/crontab :

0  1   1 * *   root   certbot renew  --manual -n --manual-public-ip-logging-ok --manual-auth-hook /root/ --manual-cleanup-hook /root/

Aditionnals Scripts

Where /root/ is


/root/ --cleanup

And in /etc/letsencrypt/renewal-hooks/post/ :


service nginx restart


If you get a 404 error with nginx, you may add this line to ensure it will not delegate treatment in other part (or send it to another webserver) :

        location /.well-known/acme-challenge/ {

Git: keep your commits in top of a branch

Thursday, 14 February 2019
Écrit par
Grégory Soutadé

Today we'll play a bit with Git. At work, we make some products that uses customized Linux kernel. Once deployed, this kernel is not often updated, so we chose to be based on LTS (Long Term Support) kernels. This gives us staibility and not so many rebase to do. Unfortunately, kernel gets security patches that we must include into our development.

But, to keep clear history, we want to have all our commits in top of the vanilla branch. Plus, having this schema helps to extract all customs patches for Yocto or other build system.

For our case, history needs to be rewrote in a non trivial way.

We currently work with version v4.14.59, but nowaday, has submitted revision v4.14.98. Lets says that we have made the following commits

6f0b0d94b3e2250551fac6ba58b5ec7a02714174 --> 0790c6bd39a86b3964d022746fc85ae2eefb824d

after tag v4.14.59. So, we have something like this :

Current git state

In our remotes we have :

  • upstream --> points to
  • origin --> internal copy of

Our branches are :

  • linux-4.14.y -> upstream/linux-4.14.y (LTS branch)
  • linux-4.14.y-custom -> origin/linux-4.14.y-custom

First, we need to update LTS branch

    git checkout linux-4.14.y
    git pull upstream linux-4.14.y
    git fetch --tags upstream

The trick here is to put the HEAD of our custom branch at the last tag without deleting our commits. So, we need to make a copy of this one.

    git checkout linux-4.14.y-custom
    git checkout -b linux-4.14.59-custom linux-4.14.y-custom

Then, cut the the HEAD and integrate vanilla work.

    git reset --hard v4.14.59
    git rebase linux-4.14.y

Finally, integrate back our commits.

    git cherry-pick 6f0b0d94b3e2250551fac6ba58b5ec7a02714174 .. 0790c6bd39a86b3964d022746fc85ae2eefb824d

The work is almost finished, we still need tu update internal tags we made ! Unlike subversion, a tag in git is just a reference to a specific commit, so it's easy to manage and update. Even if it's a shared repository, we can change them because people that uses them are focused on our custom commits and not on the ones in vanilla branch. Here is a script that get all custom tags references and apply them to the cherry picked commits. An other strategy could be to postfix tags with the new kernel revision. It's up to you to decide what better fit your needs.

The script assume all our custom tags starts with "customXXX".



nb_commits=`git log --pretty=oneline $OLD_START..$OLD_END|wc -l`

for tag in `git tag -l $TAGS_PREFIX`; do
    cur_commits=`git log --pretty=oneline $OLD_START..$tag|wc -l`
    new_commit=`git log --pretty="format:%H" -n1 --skip=$(($nb_commits - $cur_commits)) $NEW_START..HEAD`
    # git log --pretty=oneline -n1 $new_commit
    git tag -d $tag
    git tag $tag $new_commit

Last thing to do, is to sync with remote. We need to pull from origin because HEAD had a strange behavior :

    git pull origin linux-4.14.y-custom
    git push origin linux-4.14.y-custom
    git push origin linux-4.14.59-custom # Optional
    git push --force --tags origin

Force for pushing tags is not needed if the tags were not modified, but just created. Now, we can delete our copy branch or keep it into git. Don't delete it, if you want to keep your old tags references !

Final result :

Final result