top of page
  • Linkedin

Forum Posts

erik.tagirov
Oct 13, 2025
In Orchestration and Services
Infrabase https://github.com/EDGEMTech/infrabase is a lightweight build and deployment environment tailored for embedded systems development, it uses the Bitbake task orchestrator and some classes from the core layer of OpenEmbedded. It's possible to try out Infrabase - to get started follow the guide to setup the build dependencies : https://edgemtech.github.io/infrabase/user_guide.html#pre-requisites Once the setup is complete, you can get a running Linux system like so: git clone https://github.com/EDGEMTech/infrabase cd infrabase . env.sh build.sh -a bsp-linux build.sh -x qemu # Build qemu This builds everything needed to produce a system with a Linux kernel and the user space system utilities provided by Buildroot. It also builds a custom version of Qemu. Now we are ready for the deployment: sudo deploy.sh -a bsp-linux This will create an SD card image in the ./filesystem directory, it is then possible to start the image inside Qemu by running: stv # Starts the system If everything went well, you should get to the root shell. One of the strong points of Infrabase is that it handles deployment. Normally Yocto-based build solutions handle deployment operations with external scripts or tools, this is not the case in Infrabase, deployment is done inside the recipes. This is why deploy.sh invokes bitbake using sudo - normally, one is not supposed to run Bitbake as the root user! Instead it's necessary to use the fakeroot utility to imitate the root user. For example Yocto uses fakeroot to prepare archives where files need to be owned by root. Fakeroot works by using the LD_PRELOAD environment variable to load a wrapper around the getuid, chown, chmod system calls. For Infrabase however, the use of fakeroot is not sufficient as it needs full root privileges to use the mount(2) syscall to be able to mount the filesystem image using a loop device In previous versions of Infrabase the user had to add "username ALL=(ALL) NOPASSWD: ALL" to /etc/sudoers.conf during the initial setup. This was because sudo was used extensively inside deployment tasks. In recent versions, I have removed the use of sudo inside the tasks and instead have opted to execute bitbake as the root user. But this approach is not great either as it creates stamp and log files as root. Currently, we run chown at the end of each deployment task. - this is cumbersome and it's easy to forget to add a call at the end of the task that is run as root to restore file ownership. So I've been on the hunt for another solution and decided to check if it's possible to use namespaces to imitate the root user ? Turns out it is, one can start /bin/bash in a namespace where the regular user id is mapped to the root user. unshare --map-root-user /bin/dash # id uid=0(root) gid=0(root) groups=0(root),65534(nogroup) # >testing # ls -lah -rw-rw-r-- 1 root root 0 Okt 12 19:15 testing # exit ls -lah testing -rw-rw-r-- 1 etag etag 0 Okt 12 19:15 testing Inside the namespace the file is created as root, but outside it will belong to the regular user. Namespaces allow to have a different view of the file system on a per-process basis. Just like fakeroot, this technique has some limitations, it is not possible to mount/umount volumes or modify files owned by root. Processes can also join an existing namespaces with the setns(2) syscall. So one can prepare a namespace as a privileged user and then share it with other less privileged processes ? The proof of concepts I did seems to be indicating that this is possible - So I will attempt to use this technique in Infrabase in the coming weeks.
0
0
11
erik.tagirov
Sep 10, 2025
In General Discussions
#TorizonOS #virtualization #qemu #linux #eBPF During the last weeks, I was tasked with the preparation of the environment for an upcoming project. TorizonOS is going to be at the center of this project, custom drivers will have to be developed for custom hardware. We decided that it will be helpful to run TorizonOS on virtualized hardware. I was able to compile TorizonOS from source and prepare a filesystem image by adding the recipes to our internal fork of Infrabase https://github.com/EDGEMTech/infrabase/ (a build system which uses bitbake but with our own much simpler layers) I did get it to boot in about an afternoon - this is mainly because the kernel used by TorizonOS has nearly all virtualization options enabled, support for virtblk, etc But there was a pesky problem, the setsuid permission bit was missing on the /usr/bin/sudo utility. At first, I thought that the permission bit might be set by ostree on first boot. Started the investigation by reading the source code, but due to the complexity and shear amount of components - I've decided to write a tool that uses the eBPF facilities of Linux to see what process sets permissions. I've read about this method before, but never used it, always got away by using the venerable strace utility instead. Unfortunately, strace is not well suited for this scenario mainly because it works on per-process basis. Here I didn't know what performed the setting of the permissions and extended attributes. The BPF subsystem is powerful it can be used for debugging, securing/auditing and profiling at the kernel level, check the docs here: https://docs.kernel.org/bpf/index.html By using libbpf-bootstrap https://github.com/libbpf/libbpf-bootstrap and the documentation of libbpf https://docs.ebpf.io/ I came up with a simple program that can trace the chmod(2), fchmod(2), fchmodat(2) and other system calls. The tool was implemented rather quickly and I ran it on my Ubuntu 22.04 development host but it took some time to integrate into TorizonOS Yocto... Toradex do provide some articles focused on their Yocto distribution https://developer.toradex.com/linux-bsp/os-development/build-yocto/ It's a good start but you're on your own for the rest of the journey - to build that tracing tool - the journey was rather perilous... 😀 Running the tracing tool required the modification of the kernel configuration by applying some specific .scc configuration fragments, understanding how to enable systemd units, compiling the necessary build time dependencies using native recipes. At the end of it, the result is this layer : https://github.com/EDGEMTech/meta-edgemtech-bpf I basically split up the libraries used by libpbf-bootstrap into single recipes and created a main recipe for the tracing tool 'fperm-trace', there are still some parts that need some 'deeper thinking' - but it's totally usable as-is. This experience helped us gain a deeper understanding of different components used by TorizonOS and their configuration. In the end, the tracing tool helped to understand that file permissions and extended are not set on first boot... We were able to deploy the file system correctly to the filesystem image. TorizonOS now runs in Qemu and is able to reach 'Greenboot status' and Docker starts correctly. Stay tuned for more news about the internals of TorizonOS in the future.
0
0
9
erik.tagirov
Jul 28, 2025
In General Discussions
Last year while working on a personal project, I needed to find a way to embed lua scripts inside a C program. After searching the internet, I found several approaches to this problem. C23 introduced a new pre-processor directive #embed it provides a standard way to include data by using the pre-processor. However, the program I am working on is using a much older standard of the language (mainly for portability reasons). Another technique is using the xxd utility, which provides the -i option that generates a C array of unsigned chars from a supplied file echo hello! > testing.txt xxd -i testing.txt testing.h cat testing.h Produces this unsigned char testing_txt[] = { 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x21, 0x0a }; unsigned int testing_txt_len = 7; A similar result can be obtained with a custom Python script. It's a valid method that is useful for cases when a single file needs to be included, one significant limitation with this approach however, is that some editors or IDEs might not digest large C arrays when the file is opened, a freeze might occur. One has to declare the array using the extern storage identifier for it to be accessible for each element. Moreover, checking in large C array into version control, makes it slower to clone the project. I've finally decided to go with objcopy which is a very practical tool included with most compiler toolchains, it can convert any file of any size into an object file and defines <name>_start and <name>_end symbols Then by using the nm utility the name of the start/end symbols can be obtained from the produced object file and an 'array of references' can be constructed. I've wrote a simple shell script to achieve this - it generates a resources.h header file that defines an array containing all resources. https://github.com/etag4048/binembed
0
0
14
erik.tagirov
May 25, 2025
In Orchestration and Services
Configuring a DHCP server In the previous article the remote root filesystem was mounted by the kernel witch itself was loaded by U-boot over HTTP. Both the kernel and root filesystem are served from the development host. However, there is still a big issue to solve: this setup works only on my home network which has the following netid: 172.22.22.0/24. Many places use the 192.168.1.0/24 netid. In the current setup, the Pi obtains an IP address from the DHCP server running on my home router. This means that uboot.scr will have to be modified when my location changes and I have to remove the SD-Card, mount it, modify and re-generate the Uboot script, unmount - not practical at all. There are several solutions to this problem, the simplest one is to use a second dedicated network adapter and connect the Pi to it. Configure systemd-networkd to assign a static IP address to the network adapter. And in U-boot script configure the network card to use a static IP address, this way the server IP address in boot.scr can remain hardcoded. Because the netid will always be the same. A more advanced solution is to create a separate network, with a DHCP server listening ONLY on the dedicated adapter. Going forward we might need to have access to the internet on the Pi and in most scenarios, it is also generally a good idea to use DHCP to avoid manually setting static IP addresses on network cards - So I will go with this option. The downside of both approaches is that not everyone has access to a spare USB network adapter at all times. The dedicated network adapter is required for this use case as one doesn't always control of the network configuration. There is good news, support for mDNS is currently being added to U-Boot. So there might be a way to avoid using a dedicated network adapter in the future. https://github.com/u-boot/u-boot/tree/master/lib/lwip/lwip/src/apps/mdns Configuring the dedicated network adapter Ubuntu uses the systemd-networkd service to configure the network cards, my dedicated network has the following MAC address: 48:65:ee:1d:a7:51 It has been assigned the following name: enx4865ee1da751 - so let's configure it differently 😀 The network adapters are configured with files present in /etc/systemd/network Let's create a .link file for the dedicated adapter sudo vim /etc/systemd/network/15-rpi-lan.link With the following content [Match] MACAddress=48:65:ee:1d:a7:51 [Link] Name=rpilan0 MACAddress=00:11:22:33:44:55 This assigns the name rpilan0 to the network adapter and sets a custom MAC address. The setting of the custom MAC address is not mandatory, but it makes the filtering of eventual network packet captures easy - so it became a habit. Un-plug the adapter and restart systemd-networkd sudo systemctl restart systemd-networkd Plug the adapter back in: Run ip a and you should be able to locate the adapter. 10: rpilan0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 1000 link/ether 00:11:22:33:44:55 brd ff:ff:ff:ff:ff:ff permaddr 48:65:ee:1d:a7:51 The next step is to assign a static IP address to the dedicated adapter Let's create a .network file for the adapter sudo vim /etc/systemd/network/16-rpi-lan.network With the following content: [Match] Name=rpilan0 [Network] Address=10.1.1.1/24 Un-plug the adapter and restart systemd-networkd sudo systemctl restart systemd-networkd Plug the adapter and with the Pi connected and powered up so there is a signal on the Ethernet cable The output of ip a should have an entry resembling this: 10: rpilan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 00:11:22:33:44:55 brd ff:ff:ff:ff:ff:ff permaddr 48:65:ee:1d:a7:51 inet 10.1.1.1/24 brd 10.1.1.255 scope global rpilan0 valid_lft forever preferred_lft forever inet6 fe80::211:22ff:fe33:4455/64 scope link valid_lft forever preferred_lft forever Once plugged-in the adapter will always be assigned the static address 10.1.1.1 Note: The numbered prefixes are used to define the order in which the configuration files are processed by systemd-networkd, more info in the manual page systemd.network(5) Choosing and configuring a DHCP server. There are a multitude of DHCP servers available, systemd-networkd has a builtin DHCP server. Simply modify the previously created file /etc/systemd/network/16-rpi-lan.networkd add 'DHCPServer=yes' to the [Network] section Edit the Uboot script and set the serverip variable to 10.1.1.1 and everything works fine. However, I would prefer to use an external DHCP server mainly because I want to keep all configuration centralized in one place. This makes it easy to create a docker container, a chroot or a BSD jail later on. Network configuration varies from distribution to distribution so having control of the DHCP and DNS services is important. I've decided to go with dnsmasq because it suits the use case as it made for small networks and is easy to configure https://thekelleys.org.uk/dnsmasq/doc.html It is mainly a DNS forwarding and caching server but also handles DHCP/BOOTP Install it like so: apt-get install dnsmasq Disable the systemd unit: sudo systemctl disable dnsmasq A custom configuration will be provided and this also prevents it from being started by default on each boot Note: libvirt makes use of dnsmasq and by default it listens on all interfaces so check if there are no instances running like so: sudo netstat -a -p | grep ^udp | grep domain If there is no output it means that no other DNS server is running on the system dnsmasq is configured by using command line arguments of which there are many, all explained in the manual page dnsmasq(8). A configuration file can also be used check /etc/dnsmasq.conf for examples Here is the configuration file ~/src/rpi_boot/etc/dnsmasq.conf interface=rpilan0 host-record=devhost,10.1.1.1 dhcp-range=10.1.1.10,10.1.1.20,12h no-resolv no-hosts server=8.8.8.8 It only listens on the network adapter rpilan0, it can provide the address for the A record for devhost, the DHCP range has 10 available addresses. The DNS upstream server is google's DNS and reading /etc/resolv.conf is avoided as it contains the DNS server of the main network and so it requires to configure routing between the rpilan0 interface and the main network connection. This will be the focus of a subsequent article. The reading of /etc/hosts is also avoided for the same reason. To test the configuration use the -d argument to prevent dnsmasq from detaching and make it prints logs of DHCP transactions: dnsmasq -d -C ~/src/rpi_boot/etc/dnsmasq.conf Reboot the Pi - DHCP transactions should be logged dnsmasq: started, version 2.90 cachesize 150 dnsmasq: compile time options: IPv6 GNU-getopt DBus no-UBus i18n IDN2 DHCP DHCPv6 no-Lua TFTP conntrack ipset no-nftset auth cryptohash DNSSEC loop-detect inotify dumpfile dnsmasq-dhcp: DHCP, IP range 10.1.1.10 -- 10.1.1.20, lease time 12h dnsmasq: using nameserver 8.8.8.8#53 dnsmasq: cleared cache dnsmasq-dhcp: DHCPDISCOVER(rpilan0) b8:27:eb:7a:af:a3 dnsmasq-dhcp: DHCPOFFER(rpilan0) 10.1.1.14 b8:27:eb:7a:af:a3 dnsmasq-dhcp: DHCPREQUEST(rpilan0) 10.1.1.14 b8:27:eb:7a:af:a3 dnsmasq-dhcp: DHCPACK(rpilan0) 10.1.1.14 b8:27:eb:7a:af:a3 dnsmasq-dhcp: DHCPDISCOVER(rpilan0) b8:27:eb:7a:af:a3 dnsmasq-dhcp: DHCPOFFER(rpilan0) 10.1.1.14 b8:27:eb:7a:af:a3 dnsmasq-dhcp: DHCPREQUEST(rpilan0) 10.1.1.14 b8:27:eb:7a:af:a3 dnsmasq-dhcp: DHCPACK(rpilan0) 10.1.1.14 b8:27:eb:7a:af:a3 Make a test DNS request in another shell dig @10.1.1.1 devhost It should produce the following output ; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> @10.1.1.1 devhost ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42086 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1232 ;; QUESTION SECTION: ;devhost. IN A ;; ANSWER SECTION: devhost. 0 IN A 10.1.1.1 ;; Query time: 0 msec ;; SERVER: 10.1.1.1#53(10.1.1.1) (UDP) ;; WHEN: Sun May 25 11:39:17 CEST 2025 ;; MSG SIZE rcvd: 52 Re-configuring U-boot Modify .config in ~/rpi_boot/dependencies/uboot/src/.config Set CONFIG_CMD_DNS=y Re-compile Uboot to add support for the dns monitor command Update the U-Boot script like so: setenv autoload 0 dhcp dns devhost ${serverip} wget ${kernel_addr_r} /kernel/linux5.10_rpi booti ${kernel_addr_r} - ${fdt_addr} Instead of using the setenv command with a hardcoded ip address, the dns command queries the ip address of the host 'devhost' and sets the environment variable used by the wget command to fetch the kernel. Note: The kernel also needs to be re-compiled, change the root= argument to devhost in CONFIG_CMDLINE The network infrastructure is now ready and in the next article we will focus on service management and creating scripts to automate the workflow.
0
0
13
erik.tagirov
Apr 27, 2025
In Orchestration and Services
The previous article focused on the U-boot setup, fetching the kernel using HTTP with the wget U-boot monitor command. The next step is configuring the kernel to mount the root file system over the network at start. The mounting of the root file system is performed by Linux before executing the program that is specified by the init argument of the cmdline. Linux has many features that can be enabled on a case by case basis depending on the requirements of a project. It has support for a vast amount of network protocols and file systems. The usual method of mounting a root file system over the network is achieved by using the NFSv4 protocol - It is the de facto standard when it comes to sharing files over the network on UNIX-like systems. On Linux based systems, the NFS server is actually a kernel module, it is configured by modifying /etc/exports and restarting the nfs-kernel-server service. User space NFS servers do exist, but they are not mainstream. Mounting the rootfs using NFS also gets quirky due to the fact that by default the root user can not access an NFS mount point - this is by design. It can be bypassed with the use of the no_root_squash option. For this article, I've decided to use the 9P protocol, it is less complex, but on the other hand also less stable and more exotic than NFSv4. In the Linux kernel, the 9P client is provided by the v9fs kernel module. https://github.com/v9fs On the development host, I will be using diod https://github.com/chaos/diod a 9P file server which will host the root file system, diod is available as a package on Ubuntu and is easy to configure. Building the kernel Picking up in the project directory created in the previous article, add the Raspberry Pi fork of the stable kernel by adding it as a git submodule. It can be achieved using multiple git commands: git submoudle add --depth 1 https://github.com/raspberrypi/linux dependencies/linux git -C dependencies/linux fetch origin stable_20250127 --depth 1 git -C dependencies/linux checkout FETCH_HEAD git -C dependencies/linux branch rpi-6.6.y-stable FETCH_HEAD git submodule set-branch -b rpi-6.6.y-stable dependencies/linux This fetches the commit with the tag stable_20250127 without the git index history of all the commits, the tagged commit is then checked out and a local branch is created from it. Lastly, the submodule is updated to 'point' to this new local branch. At first, I tried it with a single command: git submodule add -b rpi-6.6.y --depth 1 https://github.com/raspberrypi/linux dependencies/linux But it failed to checkout the branch rpi-6.6.y due to the presence of the --depth 1 argument. The --depth 1 can be omitted if you have a lot of disk space to spare and your network connection has a decent bandwidth. But I live in the country side... 😀 We can now configure the kernel, be sure to also read the official documentation provided by RaspberryPi https://www.raspberrypi.com/documentation/computers/linux_kernel.html cd dependencies/linux KERNEL=kernel8 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2711_defconfig This generates a default configuration for RPI3+ in ARM64 mode By using menuconfig or by modifying the generated .config file modify the following Kconfig options, which by default are enabled and compiled as modules. CONFIG_NET_9P=y CONFIG_NET_9P_FD=y CONFIG_9P_FS=y CONFIG_9P_FSCACHE=y This enables built-in support for the 9P protocol and filesystem, the default configuration for the RaspberryPI enables a great quantity of modules, depending on the use case most of them can be disabled, but this is beyond the scope of this article. We will also have to configure the cmdline options accordingly: CONFIG_CMDLINE="console=tty0 root=172.22.22.57 ip=dhcp rootfstype=9p rootflags=trans=tcp,version=9p2000.L,aname=/home/etag/src/rpi_boot/out/rootfs rw init=/bin/start" CONFIG_CMDLINE_FORCE=y Specifying a hard coded IP address of the development host, the transport type, version and most importantly the path of the 9P export. Next, build the kernel and copy the resulting image to the webroot served by NGINX make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j 8 cd - # Go back to top level project directory cp dependencies/linux/arch/arm64/boot/Image out/kernel/current Configuring diod In the previous article, the web server was configured to serve the contents of the out/kernel directory, Now let's create out/rootfs which will contain the root file system, currently only a single bin directory. mkdir -p out/rootfs/bin diod can be installed by using the aptitude package manager: apt-get install diod The most simple way to configure diod is to create a configuration file in etc/diod.conf auth_required = 0 exports = { { path="/home/etag/src/rpi_boot/out/rootfs", opts="noauth" }, } Please note that there is no authentication yet - I will focus on security at later step. Start diod: sudo diod -c etc/diod.conf -S -U etag The -S argument squashes all users to the user specified with the -U argument, which owns the exported directory. Testing it Since we have no user space environment yet, we need to supply a program that will be executed by the kernel and become PID 1, this program can not use libc either since it usually comes with the environment. I found a simple program here https://peterdn.com/post/2020/08/22/hello-world-in-arm64-assembly/ It writes 'hello world!' to fd 1 using a syscall, I've added an infinite loop to avoid a panic because PID 1 is not supposed to exit. stub.S: .data /* Data segment: define our message string and calculate its length. */ msg: .ascii "Hello world!\n" len = . - msg .text /* Our application's entry point. */ .globl _start _start: /* syscall write(int fd, const void *buf, size_t count) */ mov x0, #1 /* fd := STDOUT_FILENO */ ldr x1, =msg /* buf := msg */ ldr x2, =len /* count := len */ mov w8, #64 /* write is syscall #64 */ svc #0 /* invoke syscall */ .infloop: b .infloop Assemble it and create the write the resulting test executable to the bin directory of the rootfs. aarch64-linux-gnu-as -o stub.o stub.S aarch64-linux-gnu-ld -s -o out/rootfs/bin/start stub.o Reboot the raspberryPI Should give the following output on the screen: [ 8.166134] VFS: Mounted root (9p filesystem) on device 0:18 [ 8.175286] devtmpfs: error mounting -2 [ 8.187641] Freeing unused kernel memory: 4928K [ 8.194563] Run /bin/start as init process Hello world! Conclusion The next article will focus on setting up a DHCP server and a DNS server to avoid hardcoding values in the configuration, which means that there will be 4 services running. So a service manager has to be configured to control the services. The project will eventually be pushed on Github because from the previous article, I had to adjust the URL of the kernel in the U-Boot script. And set the user name to my user in the NGINX configuration to avoid permission errors. Stayed tuned for next part!
0
0
27
erik.tagirov
Mar 23, 2025
In Orchestration and Services
To make a long story short when working with the Raspberry Pi 3 constantly removing and inserting the SD card is not practical. A good solution is network booting, hosting the root filesystem and kernel on the development host and retrieving them over the network - the SD card will only contain a bootloader. This provides an effective way to switch between different kernels and also allows work with various user space environments: GNU, busybox/buildroot, alpine/busybox, without touching the SD card. Another benefit is the improvement of the speed of the 'build, deploy, test, fix' cycle. It's not the first time that I am configuring such a setup on a RaspberryPi 3. Only this time, I am documenting it.. 😀 This article is more of a development journal - and will be split into multiple parts. The first part explains how to configure U-Boot to fetch Linux over HTTP, subsequent parts will focus on how to configure the kernel to mount a remote root filesystem. Both the kernel and the rootfs will be hosted on my development host - which is my work laptop running Ubuntu Jammy 22.04 U-Boot to the rescue By default, the Raspberry Pi 3 uses a custom bootloader and it is perfectly sufficient for most use cases. However, this use case requires a more 'feature rich' bootloader because the kernel needs to be fetched over the network. U-Boot is a popular bootloader that supports many architectures, it can fetch a kernel image over the network. Currently only HTTP and TFTP protocols are supported, HTTP support being the most recent addition. It was added back in 2022 and there has been many fixes since then. On the Rpi 3 U-Boot acts as a third-stage bootloader, the first stage, which is provided by the vendor lives in the bootcode.bin file on the SD Card, the bootloader then runs start.elf which reads config.txt and the device tree binary file, control is then given to U-Boot. Setting up the project directory: mkdir -p ~/rpi3/{dependencies,etc,volume,out} cd ~/rpi3/ git init git submodule add --depth 1 https://github.com/raspberrypi/firmware dependencies/rpi_firmware/src git submodule add --depth 1 https://github.com/u-boot/u-boot dependencies/u-boot/src mkdir creates the file hierarchy for this project. Then git downloads the pre-compiled binaries of the current Raspberry Pi Linux kernel, kernel modules, compiled device trees, user space libraries, and the bootloader/GPU firmware and also U-Boot. Submodules can be used as a practical way to manage dependencies. U-boot Nowadays, configuring U-Boot for the Raspberry Pi 3 B plus is easy. Be sure to install the required build dependencies, by following the official documentation https://docs.u-boot.org/en/latest/build/gcc.html#configuration Copy-paste the 'apt-get' commands from the documentation, it will install the GCC compiler for the aarch64 architecture and the necessary libraries required to compile U-Boot. The next step is configuring U-Boot: cd rpi3/dependencies/uboot make rpi_3_b_plus_defconfig This generates a .config file based on the default configuration for the Raspberry Pi 3 B+. If you're using another model, use: du -a configs | grep rpi_ to list the available default configurations for the various models of the Raspberry Pi. Fetching the kernel over HTTP requires the use of the 'wget' monitor command, it can be enabled by setting the CMD_WGET Kconfig option. This can be done with 'menuconfig', a ncurses configuration tool, by typing 'make menuconfig' and selecting the correct options, the .config file will be modified accordingly. Save the generated '.config' somewhere out of code tree: cp .config ~/rpi3/etc/uboot_rpi_3_b_plus.config Now that U-Boot is configured, simply run make to build it: CROSS_COMPILE=aarch64-linux-gnu- make all The compiler generates a uboot.bin file in the current directory Preparing the SD-Card Most SD cards are pre-formatted with a single FAT-32 partition, normally the Raspberry Pi 3 uses a two partition setup, one 512 Mb FAT32 boot partition and a EXT4 partition for the rootfs. Adjusting the partition layout can be achieved with the fdisk(8) utility: sudo fdisk -l This will output the list of available disk drives, ignore the '/dev/loopXX' devices of which there are many on Ubuntu... USB card readers usually use '/dev/sdX' so one can filter the output: sudo fdisk -l | grep sd.: If your system has SATA disks, locate the SD card by looking at the size. sudo fdisk /dev/sdd starts fdisk(8) in interactive mode - to manipulate the partition table of the disk use the following commands: d 1 n 1 p 1 2048 +512M t 0b w It removes the first partition, creates another primary partition of 512 Mb, sets the type to FAT32 and writes the changes to the disk and exists. Check if the partition table is correct by running fdisk -l /dev/sdd this use case requires a single FAT32 partition, sfdisk(8) can be used instead to achieve the above result from a script or build system. Next, format the partition by running: sudo mkfs.fat /dev/sdd1, mount the drive with: sudo mount /dev/sdd1 volume and copy the necessary files to the SD card: sudo cp -r dependencies/rpi_firmware/src/boot/{bootcode.bin,start.elf,fixup.dat,overlays,bcm2710-rpi-3-b-plus.dtb} volume sudo cp -r dependencies/u-boot/src/u-boot.bin volume Create the config.txt file which is read and parsed by start.elf secondary stage bootloader: cat > etc/bootloader_config.txt <<heredoc kernel=u-boot.bin arm64_bit=1 core_freq=250 device_tree=bcm2710-rpi-3-b-plus.dtb heredoc Copy the configuration to the SD Card: sudo cp etc/bootloader_config.txt volume/config.txt To understand the startup sequence and the contents of config.txt, I found these notes very useful: https://github.com/mhomran/u-boot-rpi3-b-plus The next step is to create a script for U-Boot to avoid typing monitor commands on the keyboard on each boot: cat > etc/uboot_script.txt <<heredoc setenv autoload 0 dhcp setenv serverip 172.22.22.57 wget \${kernel_addr_r} /kernel/linux5.10_rpi booti \${kernel_addr_r} - \${fdt_addr} heredoc This script interrupts the normal loading sequence, acquires an address for the Ethernet card using the DHCP client. It then performs a HTTP GET request to fetch the Linux image and store at the address specified by 'kernel_addr_r'. The request URL will look like this: 'http://172.22.22.57/linux5.10_rpi' Compile the script and copy it to the SD card: ./dependencies/u-boot/src/tools/mkimage -T script -d etc/uboot_script boot.scr sudo cp boot.scr volume/ Finally, un-mount the volume: sudo umount volume Plug in the Ethernet cable to the Raspberry Pi and insert the card - it will attempt to fetch the kernel and fail. Server setup At this stage, we have a working U-boot setup that makes use of the 'wget' monitor command to fetch the kernel and then boot it with the 'booti' command great! But a HTTP server needs to serve the kernel when the bootloader makes a HTTP GET request. Servers are supposed to have a static IP address, it would be wise to make a DHCP reservation. Setting a static IP on the Ethernet interface used by the web server is another option, To avoid 'hard to debug' conflicts it must be an IP address outside of the DHCP range of the router you're using. Both solutions have a major disadvantage, the U-Boot script will have to be modified when working in a different location. Because the network configuration will most certainly be different from one location to the next. A solution that doesn't require patching U-Boot, is to use a dedicated USB network adapter with a DHCP server listening on it. On Ubuntu, this requires a deep dive into 'systemd' and 'systemd-networkd' and then choosing the right DHCP server and configuring it... Another solution could be passing the IP address as a DHCP option, is it possible without patching U-Boot ? - I Need to investigate this by checking the source code/documentation, so it will be the focus of a subsequent article. For this guide - I will use NGINX, a famous HTTP server that I am familiar with. It can be installed like so: sudo apt-get install nginx I also like to disable the 'systemd' unit file for NGINX sudo systemctl disable nginx because I tend to run NGINX on-demand, by hand or from a script. It is also possible to run multiple instances of NGINX but this requires to specify a custom HTTP port in the configuration Create the NGINX config file: cat > etc/nginx_rpi.conf <<heredoc user www-data; worker_processes auto; pid /run/nginx.pid; events { worker_connections 768; } http { sendfile on; tcp_nopush on; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; access_log /var/log/nginx/rpi_access.log; error_log /var/log/nginx/rpi_error.log; server { listen 80 default_server; root /home/etag/rpi3/out/kernel; server_name _; location /kernel { try_files \$uri \$uri/ =404; } } } heredoc A good idea is to create the webroot in the project file hierarchy, serving the contents of the 'out/kernel' directory. Create the directory: mkdir -p out/kernel and then Start NGINX: sudo nginx -p . -c etc/nginx_config.conf It will start NGINX with the custom configuration - it requires setting the prefix with '-p' option. Reboot the Pi, while running: tail -f /var/log/nginx/rpi_access.log You should see a 'GET /kernel/linux5.10_rpi' request being logged. Conclusion To integrate this into an existing build system, scripts will need to be written but before writing them we need to make it work on other networks without modifying the U-Boot boot script each time.. The next article will focus on configuring and compiling the Raspberry Pi stable kernel, to make it mount the rootfs over the network - so stay tuned!
0
0
39
erik.tagirov
Mar 02, 2025
In General Discussions
Last week, I had to work on a project that is using the Renesas RA8D1 'Graphics micro controller'. The use of the e2studio IDE is required in this project - so I've installed it with latest FSP included and read the documentation on how use to use it. The next step was creating a Renesas account, after the successful installation on the first launch of e2studio it prompted me to login - And it failed with some "crypto error", didn't spend too much time figuring out what went wrong. To be honest, I was expecting a subpar experience because one of my colleagues had issues with running e2studio on Ubuntu 22.04, it was mostly crashing for no apparent reason. I am using the same version of Ubuntu, but a couple of months ago I've switched to a different graphical environment, and I did of course experience some crashes when using e2studio - but much less often. Due to the complexity of desktop software, there could be many reasons to this observation. Maybe he has using a slightly older version of e2studio from a couple of months ago. Searching 'Wayland + eclipse + crash' on google yields a lot of results - Wayland support is an ongoing effort for Java applications The story took a sad turn when I really needed to use JLinkRTTViewer and realized - it only has support for X11... Indeed, when I compiled the graphical environment I was lazy and couldn't be bothered including Xwayland support. So I can not run X11 applications. I will have to re-configure and recompile it with Xwayland support enabled - stuff that is typically done on the weekend if the weather is bad and there is nothing more interesting to do 😀 But I had to get work done right then and there. 😅 To get around this road block in a quick manner, I've just installed Xfce4 and logged in. Trying out e2studio in Xfce4 was a very pleasant experience, it feels much more responsive and it hasn't crashed even once. There is a J-Link progress bar that appears when flashing - that I didn't see when my colleague used e2studio on Ubuntu 22.04 The moral of the story is that from now on I will use the Xfce4 an X11 desktop environment when working on STM or Renesas projects. For me, X11 is not going away anytime soon.
0
0
31
erik.tagirov
Feb 01, 2025
In General Discussions
grep is a utility program available on any Unix-like operating system. The most common use of the grep utility, is to look for a matching string in a file or with the use of the -r option, directories of files grep -rn variable_name ./src The -n option makes it prints out line numbers and the file path relative to the current working directory Another common use is finding the large files on a system, very practical when one runs out of storage space. du -h ~/Downloads | grep ^[0-9.]*[G] 1.1G /home/etag/Downloads/snap/firefox/common/.cache/mozilla/firefox/szwshp2r.default/cache2/entries 1.1G /home/etag/Downloads/snap/firefox/common/.cache/mozilla/firefox/szwshp2r.default/cache2 1.1G /home/etag/Downloads/snap/firefox/common/.cache/mozilla/firefox/szwshp2r.default 1.1G /home/etag/Downloads/snap/firefox/common/.cache/mozilla/firefox 1.1G /home/etag/Downloads/snap/firefox/common/.cache/mozilla 1.1G /home/etag/Downloads/snap/firefox/common/.cache 1.9G /home/etag/Downloads/snap/firefox/common 1.9G /home/etag/Downloads/snap/firefox 1.3G /home/etag/Downloads/snap/teams-for-linux 3.8G /home/etag/Downloads/snap 6.7G /home/etag/Downloads/archives 1.4G /home/etag/Downloads/cube_mx 31G /home/etag/Downloads/ It printed all directories that are larger than 1 gigabyte du is a utility that estimates the storage space of files or directories, in this example only directories and the -h option makes it print the size in a human friendly format. The output of the du utility is then passed to the standard input of grep which filters it by applying a simple regular expression ^[0-9.]*G The text must begin with any number of characters matching the range 0-9 and the dot . followed by a single capital letter G The caret ^ means 'beginning of the line' The brackets [0-9.] are used to specify a character class, ranges can be used like 'A-z' 'A-Z' '0-9' which is of course more practical than typing out [0123456789] each time. The asterisk * means zero or more occurrences of the character or character class, if the * is omitted in the above example it would only match lines beginning with a single digit or dot and followed by a G * always applies to the previous character or character class 1G 9G .G Since the du utility also prints out file names, I often use it to get a list of all files names of a particular extension. While I agree that the find utility is probably faster and more optimized, my problem with it is that I can never remember the syntax or options :) So I tend to use it only in scripts and lookup the manual page each time. du -a src | grep "\.h$" This outputs a list of all C header files present in the src directory The dollar sign $ means 'end of the line' The dot . 'means any single character' Consider the following command du -a src | grep .h$ It would nearly achieve the desired result, it will also print out files ending in sh so shell scripts and C header files. To match lines ending in '.h' the dot has to be escaped with a backslash \ to represent the actual character. Note that the quotes are necessary here, because the shell also makes use of the backslash to escape characters that have a 'special meaning'. One can also use the variant below to prevent the shell from interpreting the backslash du -a src | grep \\.h$ The dot is a very powerful concept, let's print out the first 10 words in the English dictionary that begin with 'th' followed by any 3 letters grep ^th...$ /usr/dict/en/words.txt | head -n 10 thack thais thala thana thane thank tharf tharm thatd thatn One can find some unusual words :) Another common everyday use of the caret is filtering the output of ls -l to show only directories ls -l ~/src/lvgl | grep ^d drwxrwxr-x 15 etag etag 4096 Dec 28 13:56 demos drwxrwxr-x 9 etag etag 4096 Dec 28 13:56 docs drwxrwxr-x 8 etag etag 4096 Dec 16 13:17 env_support drwxrwxr-x 14 etag etag 4096 Dec 16 13:17 examples drwxrwxr-x 3 etag etag 4096 Dec 16 13:17 libs drwxrwxr-x 5 etag etag 4096 Dec 28 13:56 scripts drwxrwxr-x 17 etag etag 4096 Dec 28 13:56 src drwxrwxr-x 11 etag etag 4096 Dec 28 13:56 tests drwxrwxr-x 2 etag etag 4096 Dec 28 13:56 xmls drwxrwxr-x 2 etag etag 4096 Dec 16 13:17 zephyr To only show files, one can use the -v option to invert the meaning of the expression ls -l ~/src/lvgl | grep -v ^d | head -n 6 total 232 -rw-rw-r-- 1 etag etag 1518 Dec 28 13:56 CMakeLists.txt -rw-rw-r-- 1 etag etag 2818 Dec 16 13:17 CMakePresets.json -rw-rw-r-- 1 etag etag 2508 Dec 16 13:17 component.mk -rw-rw-r-- 1 etag etag 204 Dec 16 13:17 idf_component.yml -rw-rw-r-- 1 etag etag 57414 Dec 28 13:56 Kconfig But the if total amount is not needed: ls -l ~/src/lvgl | grep ^- | head -n 6 When working with LVGL, to locate the definition of a function this expression can be used grep -rn "^[a-z_\ *]*\ lv_obj_set_size.*[^;/]$" src src/core/lv_obj_pos.c:213:void lv_obj_set_size(lv_obj_t * obj, int32_t w, int32_t h) Let's break it down, the line must begin with one or more characters defined by the character class [a-z_\ *] which allows letters underscores spaces and the * character So, it covers storage specifiers, return types and pointers. There must be at least a space before the function name which can be followed by any number of characters but it must not end with ; or / With the caret ^ omitted from the [;/] character class, it will output the declaration and calls to the function, this is because the caret inverts the meaning of the character class grep -rn "^[a-z_\ *]*\ lv_obj_create.*[;]$" src src/core/lv_obj.h:229:lv_obj_t * lv_obj_create(lv_obj_t * parent); src/widgets/tabview/lv_tabview.c:277: lv_obj_create(obj); tests/src/test_cases/widgets/test_obj_tree.c:18: lv_obj_create(lv_screen_active()); tests/src/test_cases/widgets/test_obj_tree.c:20: lv_obj_create(lv_screen_active()); To only show declarations, filter it more grep -rn "^[a-z_\ *]*\ lv_obj_create.*[;]$" src | grep \\.h src/core/lv_obj.h:229:lv_obj_t * lv_obj_create(lv_obj_t * parent); This expression works because the coding convention used by LVGL states that the opening brace { of a function body always goes on the line below The / is needed because it could match a calls instead something(); /* some comment */ Usually expressions depend on the style of the code some projects, have return types and storage specifiers on a separate line above the function name void myfunction(int x, int y) { /* body */ } In such projects the following expression can be used to locate the definition grep -rn ^myfunction src There are many more use cases for grep, it is a very practical tool that I use on a daily basis. To filter logs, find occurrences and extract matching strings with the -o option the only catch is that it can only perform pattern matching on one line at a time.
0
0
26
bottom of page