909 lines
25 KiB
Markdown
909 lines
25 KiB
Markdown
# Yocto FCB1010 Looper System - Implementation Guide
|
|
|
|
## Project Overview
|
|
|
|
Build a minimal, real-time Linux system for an audio looper pedal using Yocto Project. The system boots from initramfs only for maximum speed, includes RT kernel patches, and supports over-the-air updates via SWUpdate with A/B partitioning.
|
|
|
|
**Key Requirements:**
|
|
- x64 hardware (NUC-class)
|
|
- Static IP: 192.168.0.50
|
|
- SSH access with public key authentication
|
|
- USB live installer that can dd copy to disk
|
|
- A/B partition scheme for reliable updates
|
|
- RT kernel with PREEMPT_RT
|
|
- JACK audio stack
|
|
- SWUpdate for OTA updates
|
|
|
|
## Architecture Decisions
|
|
|
|
**Boot Strategy:** EFI boot → initramfs-only (no rootfs mounting)
|
|
**Network:** WiFi with static IP, credentials baked into image
|
|
**Updates:** SWUpdate web interface + SSH API access
|
|
**Installation:** USB boot → SSH → dd copy entire USB to target disk
|
|
**Partition Layout:** EFI boot partition with initramfs-A.cpio.gz and initramfs-B.cpio.gz
|
|
|
|
## Yocto Implementation Approach
|
|
|
|
**Custom Layer Structure:**
|
|
Create meta-looper with recipes for kernel config (RT patches), network setup (static IP + WiFi), SSH keys, audio stack (JACK), and two image types: main system (initramfs) and USB installer.
|
|
|
|
**Build Configuration:**
|
|
- MACHINE = "genericx86-64"
|
|
- Kernel: linux-yocto with RT features
|
|
- Minimal install, not Pokey distribution
|
|
|
|
## Key Implementation Points
|
|
|
|
**Kernel:** Enable PREEMPT_RT, high-res timers, and audio subsystem. Use kernel fragment files (.cfg) to configure RT options.
|
|
|
|
**Networking:** Create recipes for wpa_supplicant.conf and network interfaces with static IP. Include WiFi credentials at build time.
|
|
|
|
**SSH:** Recipe to install public key to root's authorized_keys during image build.
|
|
|
|
**SWUpdate:** Configure for A/B updates, web interface on port 8080, and integration with systemd-boot for partition switching.
|
|
|
|
**USB Installer:** Second image that boots live system with installation script. Script partitions target disk, creates EFI boot partition, and dd copies the installer image to create installed system.
|
|
|
|
## Running
|
|
The build and testing platform is built in Docker. A Compose file is used to script the separate targets:
|
|
- docker compose run disk-image
|
|
- docker compose run qemu-server
|
|
- docker compose run push-update
|
|
- docker compose run ssh-client
|
|
|
|
The compose file mounts the proper directories for cache, output, ... It also forwards the proper ports for qemu.
|
|
|
|
The run script wraps docker compose run and adds some environment variables.
|
|
|
|
## Testing Strategy for AI Agent
|
|
|
|
**QEMU Testing:** Use runqemu with network forwarding to test SSH, networking, and SWUpdate functionality. QEMU supports graphics for GUI testing and USB forwarding for hardware validation.
|
|
|
|
**Build Validation:** Verify initramfs size (<200MB), boot time (<10 seconds), and RT kernel configuration. Test static IP assignment and SSH key authentication.
|
|
|
|
**Update Testing:** Create test SWUpdate packages and verify A/B switching works correctly. Test both web interface and SSH-based update deployment.
|
|
|
|
**Audio Validation:** Confirm JACK installation, RT scheduling capabilities with cyclictest, and audio device detection.
|
|
|
|
**Installation Testing:** Boot USB installer in QEMU, SSH in, run installation script on virtual disk, then boot installed system to verify complete workflow.
|
|
|
|
The implementation should focus on creating minimal, working recipes that can be tested incrementally in QEMU before hardware deployment. Each component (kernel, network, SSH, audio, updates) should be testable independently.
|
|
|
|
## Core Architecture and Directory Structure
|
|
|
|
The system integrates **Yocto Project 5.0 Scarthgap LTS** with Docker containerization, organizing all components within a Rust workspace directory structure. This approach minimizes host dependencies while providing a robust embedded Linux development environment with reliable over-the-air updates.
|
|
|
|
### Project Organization Within Rust Workspace
|
|
|
|
```
|
|
image/
|
|
├── Dockerfile
|
|
├── docker-compose.yml
|
|
├── run
|
|
│
|
|
├── yocto/
|
|
│ ├── poky/
|
|
│ ├── meta-openembedded/
|
|
│ ├── meta-rust-bin/ # Pre-built Rust toolchain
|
|
│ └── meta-swupdate/ # SWUpdate integration
|
|
├── meta-layers/
|
|
│ ├── meta-custom/ # Custom project layer
|
|
│ │ ├── conf/
|
|
│ │ │ ├── distro/
|
|
│ │ │ ├── machine/
|
|
│ │ │ └── layer.conf
|
|
│ │ ├── recipes-apps/
|
|
│ │ │ └── egui-kiosk/
|
|
│ │ ├── recipes-core/
|
|
│ │ │ └── images/
|
|
│ │ └── recipes-support/
|
|
│ │ └── swupdate/
|
|
│ └── meta-custom-bsp/ # BSP-specific configurations
|
|
├── scripts/
|
|
│ ├── setup-environment.sh
|
|
│ ├── build-images.sh
|
|
│ ├── deploy-qemu.sh
|
|
│ └── create-update.sh
|
|
└── conf/
|
|
├── local.conf
|
|
└── bblayers.conf
|
|
```
|
|
|
|
The **Rust workspace configuration** unifies all application components:
|
|
|
|
```toml
|
|
# rust-workspace/Cargo.toml
|
|
[workspace]
|
|
resolver = "2"
|
|
members = [
|
|
"apps/*",
|
|
"libs/*"
|
|
]
|
|
|
|
[workspace.package]
|
|
version = "1.0.0"
|
|
edition = "2021"
|
|
rust-version = "1.70"
|
|
|
|
[workspace.dependencies]
|
|
egui = "0.24"
|
|
eframe = { version = "0.24", features = ["wgpu"] }
|
|
serde = { version = "1.0", features = ["derive"] }
|
|
tokio = { version = "1.0", features = ["rt-multi-thread"] }
|
|
```
|
|
|
|
## Yocto Layer Creation and Rust Integration
|
|
|
|
The custom layer provides comprehensive Rust application support with modern toolchain integration using **meta-rust-bin** for faster builds and easier maintenance.
|
|
|
|
### Custom Layer Configuration
|
|
|
|
```bash
|
|
# meta-layers/meta-custom/conf/layer.conf
|
|
BBPATH .= ":${LAYERDIR}"
|
|
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb ${LAYERDIR}/recipes-*/*/*.bbappend"
|
|
|
|
BBFILE_COLLECTIONS += "custom-layer"
|
|
BBFILE_PATTERN_custom-layer = "^${LAYERDIR}/"
|
|
BBFILE_PRIORITY_custom-layer = "10"
|
|
|
|
LAYERVERSION_custom-layer = "1"
|
|
LAYERSERIES_COMPAT_custom-layer = "scarthgap"
|
|
|
|
LAYERDEPENDS_custom-layer = "core openembedded-layer meta-rust-bin"
|
|
```
|
|
|
|
### Rust egui Application Recipe
|
|
|
|
The recipe integrates seamlessly with the Rust workspace, handling cross-compilation and dependencies automatically:
|
|
|
|
```bash
|
|
# meta-layers/meta-custom/recipes-apps/egui-kiosk/egui-kiosk_1.0.bb
|
|
SUMMARY = "Rust egui Fullscreen Kiosk Application"
|
|
DESCRIPTION = "Cross-platform GUI kiosk application built with egui framework"
|
|
LICENSE = "MIT"
|
|
LIC_FILES_CHKSUM = "file://LICENSE;md5=935a9b2a57ae70704d8125b9c0e39059"
|
|
|
|
inherit cargo_bin systemd
|
|
|
|
# Enable network access for cargo dependencies
|
|
do_compile[network] = "1"
|
|
|
|
SRC_URI = "\
|
|
git://localhost/rust-workspace.git;protocol=file;branch=main \
|
|
file://egui-kiosk.service \
|
|
file://egui-kiosk-init.sh \
|
|
file://weston.ini \
|
|
"
|
|
|
|
SRCREV = "${AUTOREV}"
|
|
S = "${WORKDIR}/git"
|
|
|
|
# Point to specific app in workspace
|
|
CARGO_SRC_DIR = "apps/egui-kiosk"
|
|
|
|
# Cargo features for kiosk mode
|
|
CARGO_FEATURES = "kiosk-mode fullscreen wayland"
|
|
|
|
# Runtime dependencies for GUI applications
|
|
RDEPENDS_${PN} += "\
|
|
wayland \
|
|
weston \
|
|
wayland-protocols \
|
|
libxkbcommon \
|
|
fontconfig \
|
|
liberation-fonts \
|
|
systemd \
|
|
"
|
|
|
|
# Build dependencies
|
|
DEPENDS += "\
|
|
wayland-native \
|
|
wayland-protocols-native \
|
|
libxkbcommon \
|
|
fontconfig \
|
|
"
|
|
|
|
# Install systemd service and configuration
|
|
do_install_append() {
|
|
install -d ${D}${systemd_system_unitdir}
|
|
install -m 0644 ${WORKDIR}/egui-kiosk.service ${D}${systemd_system_unitdir}/
|
|
|
|
install -d ${D}${bindir}
|
|
install -m 0755 ${WORKDIR}/egui-kiosk-init.sh ${D}${bindir}/
|
|
|
|
install -d ${D}${sysconfdir}/xdg/weston
|
|
install -m 0644 ${WORKDIR}/weston.ini ${D}${sysconfdir}/xdg/weston/
|
|
}
|
|
|
|
SYSTEMD_SERVICE_${PN} = "egui-kiosk.service"
|
|
SYSTEMD_AUTO_ENABLE_${PN} = "enable"
|
|
|
|
FILES_${PN} += "\
|
|
${systemd_system_unitdir}/egui-kiosk.service \
|
|
${bindir}/egui-kiosk-init.sh \
|
|
${sysconfdir}/xdg/weston/weston.ini \
|
|
"
|
|
```
|
|
|
|
## Rust egui Application Implementation
|
|
|
|
The application leverages **egui 0.24** with fullscreen kiosk capabilities and system integration:
|
|
|
|
```rust
|
|
// rust-workspace/apps/egui-kiosk/src/main.rs
|
|
use eframe::{egui, App, Frame, NativeOptions};
|
|
use std::env;
|
|
|
|
struct KioskApp {
|
|
counter: i32,
|
|
show_confirmation_dialog: bool,
|
|
}
|
|
|
|
impl KioskApp {
|
|
fn new() -> Self {
|
|
Self {
|
|
counter: 0,
|
|
show_confirmation_dialog: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl App for KioskApp {
|
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut Frame) {
|
|
egui::CentralPanel::default().show(ctx, |ui| {
|
|
ui.heading("Production Kiosk System");
|
|
|
|
ui.add_space(20.0);
|
|
|
|
ui.horizontal(|ui| {
|
|
if ui.button("Increment").clicked() {
|
|
self.counter += 1;
|
|
}
|
|
ui.label(format!("Counter: {}", self.counter));
|
|
});
|
|
|
|
ui.add_space(20.0);
|
|
|
|
if ui.button("System Settings").clicked() {
|
|
self.show_confirmation_dialog = true;
|
|
}
|
|
});
|
|
|
|
if self.show_confirmation_dialog {
|
|
egui::Window::new("Confirm Action")
|
|
.collapsible(false)
|
|
.resizable(false)
|
|
.show(ctx, |ui| {
|
|
ui.label("Are you sure you want to access system settings?");
|
|
ui.horizontal(|ui| {
|
|
if ui.button("Yes").clicked() {
|
|
// Handle system settings access
|
|
self.show_confirmation_dialog = false;
|
|
}
|
|
if ui.button("No").clicked() {
|
|
self.show_confirmation_dialog = false;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
fn main() -> Result<(), eframe::Error> {
|
|
let options = NativeOptions {
|
|
fullscreen: true,
|
|
decorated: false,
|
|
resizable: false,
|
|
always_on_top: true,
|
|
..Default::default()
|
|
};
|
|
|
|
eframe::run_native(
|
|
"Kiosk Application",
|
|
options,
|
|
Box::new(|_cc| Box::new(KioskApp::new())),
|
|
)
|
|
}
|
|
```
|
|
|
|
## Systemd Service Configuration for Auto-Start
|
|
|
|
The systemd service ensures reliable application startup with proper dependencies and restart capabilities:
|
|
|
|
```ini
|
|
# files/egui-kiosk.service
|
|
[Unit]
|
|
Description=Rust egui Kiosk Application
|
|
Documentation=https://github.com/company/egui-kiosk
|
|
After=graphical.target weston.service
|
|
Wants=weston.service
|
|
Requires=graphical.target
|
|
StartLimitIntervalSec=30
|
|
StartLimitBurst=3
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=kiosk
|
|
Group=kiosk
|
|
Environment="DISPLAY=:0"
|
|
Environment="WAYLAND_DISPLAY=wayland-0"
|
|
Environment="XDG_RUNTIME_DIR=/run/user/1000"
|
|
Environment="RUST_LOG=info"
|
|
|
|
ExecStartPre=/bin/sleep 3
|
|
ExecStart=/usr/bin/egui-kiosk
|
|
ExecReload=/bin/kill -HUP $MAINPID
|
|
|
|
Restart=always
|
|
RestartSec=5
|
|
WatchdogSec=30
|
|
|
|
# Resource limits
|
|
MemoryMax=256M
|
|
CPUQuota=80%
|
|
|
|
# Security settings
|
|
NoNewPrivileges=true
|
|
PrivateTmp=true
|
|
ProtectSystem=strict
|
|
ProtectHome=true
|
|
ReadWritePaths=/tmp /run/user/1000
|
|
|
|
[Install]
|
|
WantedBy=graphical.target
|
|
```
|
|
|
|
## initramfs Integration Strategy
|
|
|
|
The initramfs approach embeds the Rust application directly in the boot image for fastest startup times and minimal attack surface.
|
|
|
|
### initramfs Image Recipe
|
|
|
|
```bash
|
|
# meta-layers/meta-custom/recipes-core/images/initramfs-egui-image.bb
|
|
DESCRIPTION = "Minimal initramfs image with Rust GUI application"
|
|
|
|
PACKAGE_INSTALL = "\
|
|
initramfs-framework-base \
|
|
busybox \
|
|
base-files \
|
|
egui-kiosk \
|
|
weston \
|
|
liberation-fonts \
|
|
${VIRTUAL-RUNTIME_base-utils} \
|
|
"
|
|
|
|
# Minimize image size
|
|
IMAGE_LINGUAS = ""
|
|
LICENSE = "MIT"
|
|
|
|
inherit core-image
|
|
|
|
# Optimize for minimal size
|
|
IMAGE_ROOTFS_SIZE ?= "32768"
|
|
IMAGE_ROOTFS_EXTRA_SPACE = "0"
|
|
IMAGE_OVERHEAD_FACTOR = "1.0"
|
|
|
|
# Bundle with kernel
|
|
INITRAMFS_IMAGE_BUNDLE = "1"
|
|
```
|
|
|
|
### initramfs Boot Module
|
|
|
|
```bash
|
|
# meta-layers/meta-custom/recipes-core/initramfs-egui-boot/files/egui-boot
|
|
#!/bin/sh
|
|
|
|
egui_boot_enabled() {
|
|
return 0
|
|
}
|
|
|
|
egui_boot_run() {
|
|
msg "Starting GUI boot sequence..."
|
|
|
|
# Mount essential filesystems
|
|
mount -t proc proc /proc
|
|
mount -t sysfs sysfs /sys
|
|
mount -t devtmpfs devtmpfs /dev
|
|
|
|
# Setup runtime directories
|
|
mkdir -p /run/user/1000
|
|
chown 1000:1000 /run/user/1000
|
|
|
|
# Start Wayland compositor
|
|
export XDG_RUNTIME_DIR="/run/user/1000"
|
|
export WAYLAND_DISPLAY="wayland-0"
|
|
|
|
weston --backend=drm-backend.so --idle-time=0 &
|
|
sleep 2
|
|
|
|
# Start Rust GUI application
|
|
egui-kiosk &
|
|
|
|
msg "GUI application started successfully"
|
|
}
|
|
```
|
|
|
|
## SWUpdate A/B Partitioning System
|
|
|
|
The A/B update system provides **atomic updates with automatic rollback**, ensuring system reliability even during power failures or failed updates.
|
|
|
|
### Partition Layout Configuration
|
|
|
|
```bash
|
|
# Recommended eMMC/SSD partition layout
|
|
/dev/sda
|
|
├── sda1 # Boot partition (EFI/BIOS, 64MB)
|
|
├── sda2 # RootFS A (ext4, 2GB)
|
|
├── sda3 # RootFS B (ext4, 2GB)
|
|
├── sda4 # Data partition (ext4, remaining)
|
|
└── sda5 # U-Boot environment (1MB)
|
|
```
|
|
|
|
### WIC Image Configuration for A/B Layout
|
|
|
|
```bash
|
|
# meta-layers/meta-custom/wic/dual-rootfs.wks
|
|
# Boot partition with bootloader
|
|
part /boot --source bootimg-partition --sourceparams="loader=u-boot" \
|
|
--ondisk sda --fstype=vfat --label boot --size 64M --align 1024
|
|
|
|
# Root filesystem A (active partition)
|
|
part / --source rootfs --rootfs-dir=${IMAGE_ROOTFS} \
|
|
--ondisk sda --fstype=ext4 --label rootfs_a --size 2G
|
|
|
|
# Root filesystem B (update target, initially empty)
|
|
part --ondisk sda --fstype=ext4 --label rootfs_b --size 2G
|
|
|
|
# Persistent data partition
|
|
part /data --ondisk sda --fstype=ext4 --label data --size 1G \
|
|
--fsoptions="defaults"
|
|
|
|
# U-Boot environment for A/B switching
|
|
part --ondisk sda --size 1M --label uboot_env
|
|
|
|
bootloader u-boot
|
|
```
|
|
|
|
### SWUpdate Configuration
|
|
|
|
```libconfig
|
|
# files/sw-description
|
|
software =
|
|
{
|
|
version = "1.0.0";
|
|
description = "Dual-copy Rust GUI system update";
|
|
|
|
hardware-compatibility: [ "1.0", "2.0" ];
|
|
|
|
stable = {
|
|
copy1 = {
|
|
images: (
|
|
{
|
|
filename = "core-image-egui-minimal.ext4.gz";
|
|
type = "raw";
|
|
compressed = true;
|
|
device = "/dev/sda2";
|
|
sha256 = "$swupdate_get_sha256(core-image-egui-minimal.ext4.gz)";
|
|
}
|
|
);
|
|
|
|
scripts: (
|
|
{
|
|
filename = "post-update.sh";
|
|
type = "shellscript";
|
|
}
|
|
);
|
|
|
|
bootenv: (
|
|
{
|
|
name = "rootfs_part";
|
|
value = "2";
|
|
},
|
|
{
|
|
name = "upgrade_available";
|
|
value = "1";
|
|
}
|
|
);
|
|
};
|
|
|
|
copy2 = {
|
|
images: (
|
|
{
|
|
filename = "core-image-egui-minimal.ext4.gz";
|
|
type = "raw";
|
|
compressed = true;
|
|
device = "/dev/sda3";
|
|
sha256 = "$swupdate_get_sha256(core-image-egui-minimal.ext4.gz)";
|
|
}
|
|
);
|
|
|
|
scripts: (
|
|
{
|
|
filename = "post-update.sh";
|
|
type = "shellscript";
|
|
}
|
|
);
|
|
|
|
bootenv: (
|
|
{
|
|
name = "rootfs_part";
|
|
value = "3";
|
|
},
|
|
{
|
|
name = "upgrade_available";
|
|
value = "1";
|
|
}
|
|
);
|
|
};
|
|
};
|
|
|
|
scripts: (
|
|
{
|
|
filename = "partition-select.lua";
|
|
type = "lua";
|
|
}
|
|
);
|
|
}
|
|
```
|
|
|
|
### Automatic Partition Selection Logic
|
|
|
|
```lua
|
|
-- files/partition-select.lua
|
|
function preinst()
|
|
-- Determine current root partition
|
|
local f = io.open("/proc/cmdline", "r")
|
|
local cmdline = f:read("*all")
|
|
f:close()
|
|
|
|
local current_part = cmdline:match("root=/dev/sda(%d)")
|
|
|
|
if current_part == "2" then
|
|
-- Currently on partition 2, update partition 3
|
|
swupdate.notify(RECOVERY, "Updating partition 3 (copy2)")
|
|
return 0, "copy2"
|
|
else
|
|
-- Currently on partition 3 or default, update partition 2
|
|
swupdate.notify(RECOVERY, "Updating partition 2 (copy1)")
|
|
return 0, "copy1"
|
|
end
|
|
end
|
|
|
|
function postinst()
|
|
swupdate.notify(RECOVERY, "Update installation completed")
|
|
return 0
|
|
end
|
|
```
|
|
|
|
## QEMU Testing Configuration
|
|
|
|
QEMU provides comprehensive testing capabilities with **KVM acceleration** and proper graphics support for egui application validation.
|
|
|
|
### QEMU Launch Configuration
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# scripts/deploy-qemu.sh
|
|
|
|
set -e
|
|
|
|
IMAGE_PATH="yocto/build/tmp/deploy/images/qemux86-64"
|
|
KERNEL="${IMAGE_PATH}/bzImage"
|
|
ROOTFS="${IMAGE_PATH}/core-image-egui-minimal-qemux86-64.ext4"
|
|
|
|
# Launch QEMU with graphics support
|
|
qemu-system-x86_64 \
|
|
-enable-kvm \
|
|
-m 2048 \
|
|
-smp 4 \
|
|
-cpu host \
|
|
-kernel "${KERNEL}" \
|
|
-drive file="${ROOTFS}",if=virtio,format=raw \
|
|
-append "root=/dev/vda rw console=ttyS0 console=tty0 quiet" \
|
|
-netdev user,id=net0,hostfwd=tcp::8080-:8080,hostfwd=tcp::2222-:22 \
|
|
-device virtio-net-pci,netdev=net0 \
|
|
-device virtio-vga-gl \
|
|
-display gtk,gl=on \
|
|
-audiodev pulse,id=audio0 \
|
|
-device intel-hda \
|
|
-device hda-duplex,audiodev=audio0 \
|
|
-serial stdio \
|
|
-show-cursor
|
|
```
|
|
|
|
### Automated Testing Framework
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# scripts/test-system.sh
|
|
|
|
test_boot_time() {
|
|
echo "Testing boot time..."
|
|
start_time=$(date +%s.%N)
|
|
|
|
timeout 60 qemu-system-x86_64 \
|
|
-enable-kvm -m 1024 -nographic \
|
|
-kernel "${KERNEL}" \
|
|
-drive file="${ROOTFS}",if=virtio,format=raw \
|
|
-append "root=/dev/vda rw console=ttyS0 init=/bin/sh" &
|
|
|
|
QEMU_PID=$!
|
|
wait $QEMU_PID
|
|
|
|
end_time=$(date +%s.%N)
|
|
boot_time=$(echo "$end_time - $start_time" | bc)
|
|
|
|
echo "Boot time: ${boot_time} seconds"
|
|
if (( $(echo "$boot_time < 15" | bc -l) )); then
|
|
echo "PASS: Boot time acceptable"
|
|
else
|
|
echo "FAIL: Boot time too slow"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
test_application_startup() {
|
|
echo "Testing application startup..."
|
|
# Launch system and verify egui application starts
|
|
timeout 120 expect -c '
|
|
spawn qemu-system-x86_64 -enable-kvm -m 1024 -nographic \
|
|
-kernel '"${KERNEL}"' \
|
|
-drive file='"${ROOTFS}"',if=virtio,format=raw \
|
|
-append "root=/dev/vda rw console=ttyS0"
|
|
|
|
expect "login:" { send "root\r" }
|
|
expect "# " { send "systemctl status egui-kiosk\r" }
|
|
expect "active (running)" {
|
|
puts "SUCCESS: egui application running"
|
|
exit 0
|
|
}
|
|
timeout {
|
|
puts "FAIL: Application not started in time"
|
|
exit 1
|
|
}
|
|
'
|
|
}
|
|
|
|
test_boot_time
|
|
test_application_startup
|
|
echo "All tests passed!"
|
|
```
|
|
|
|
## Minimal Linux System Configuration
|
|
|
|
The system optimizes for **minimal resource usage** while maintaining essential functionality for the Rust GUI application.
|
|
|
|
### Minimal Image Recipe
|
|
|
|
```bash
|
|
# meta-layers/meta-custom/recipes-core/images/core-image-egui-minimal.bb
|
|
SUMMARY = "Minimal Linux system with Rust egui application"
|
|
|
|
IMAGE_INSTALL = "\
|
|
packagegroup-core-boot \
|
|
egui-kiosk \
|
|
weston \
|
|
liberation-fonts \
|
|
systemd \
|
|
networkmanager \
|
|
swupdate \
|
|
${CORE_IMAGE_EXTRA_INSTALL} \
|
|
"
|
|
|
|
# Remove unnecessary features
|
|
DISTRO_FEATURES_remove = "alsa bluetooth wifi nfs zeroconf pci 3g nfc x11"
|
|
IMAGE_FEATURES = "read-only-rootfs"
|
|
|
|
# Package optimization
|
|
PACKAGE_CLASSES = "package_ipk"
|
|
IMAGE_FSTYPES = "ext4 ext4.gz wic"
|
|
|
|
# Size constraints
|
|
IMAGE_ROOTFS_SIZE ?= "512000" # 512MB
|
|
IMAGE_OVERHEAD_FACTOR = "1.1"
|
|
|
|
LICENSE = "MIT"
|
|
|
|
inherit core-image
|
|
|
|
# Remove debug packages
|
|
PACKAGE_EXCLUDE = "\
|
|
gdb \
|
|
gdbserver \
|
|
strace \
|
|
${@bb.utils.contains('DISTRO_FEATURES', 'x11', 'x11-common', '', d)} \
|
|
"
|
|
|
|
# Optimize systemd
|
|
IMAGE_INSTALL_append = " systemd-analyze"
|
|
SYSTEMD_DEFAULT_TARGET = "graphical.target"
|
|
```
|
|
|
|
### System Optimization Configuration
|
|
|
|
```bash
|
|
# meta-layers/meta-custom/recipes-core/systemd/systemd/system.conf
|
|
[Manager]
|
|
LogLevel=warning
|
|
LogTarget=kmsg
|
|
SystemMaxUse=50M
|
|
RuntimeMaxUse=50M
|
|
DefaultTimeoutStartSec=10s
|
|
DefaultTimeoutStopSec=5s
|
|
DefaultRestartSec=5s
|
|
```
|
|
|
|
## Build and Deployment Workflows
|
|
|
|
The complete workflow integrates Docker containerization with automated builds, testing, and deployment.
|
|
|
|
### Environment Setup Script
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# scripts/setup-environment.sh
|
|
|
|
set -e
|
|
|
|
echo "Setting up Yocto development environment..."
|
|
|
|
# Export user ID for Docker
|
|
export USER_ID=$(id -u)
|
|
export GROUP_ID=$(id -g)
|
|
|
|
# Initialize Git repositories if not present
|
|
if [ ! -d "yocto/sources/poky" ]; then
|
|
echo "Cloning Yocto repositories..."
|
|
mkdir -p yocto/sources
|
|
cd yocto/sources
|
|
|
|
git clone git://git.yoctoproject.org/poky -b scarthgap
|
|
git clone git://git.openembedded.org/meta-openembedded -b scarthgap
|
|
git clone https://github.com/rust-embedded/meta-rust-bin.git -b master
|
|
git clone https://github.com/sbabic/meta-swupdate.git -b scarthgap
|
|
|
|
cd ../..
|
|
fi
|
|
|
|
# Start Docker environment
|
|
echo "Starting containerized build environment..."
|
|
docker-compose up -d yocto-builder
|
|
|
|
# Wait for container to be ready
|
|
sleep 5
|
|
|
|
# Initialize build environment inside container
|
|
docker-compose exec yocto-builder bash -c "
|
|
cd /workspace/yocto &&
|
|
source sources/poky/oe-init-build-env build &&
|
|
|
|
# Configure layers
|
|
bitbake-layers add-layer ../sources/meta-openembedded/meta-oe
|
|
bitbake-layers add-layer ../sources/meta-openembedded/meta-python
|
|
bitbake-layers add-layer ../sources/meta-openembedded/meta-networking
|
|
bitbake-layers add-layer ../sources/meta-rust-bin
|
|
bitbake-layers add-layer ../sources/meta-swupdate
|
|
bitbake-layers add-layer ../../meta-layers/meta-custom
|
|
|
|
# Configure build
|
|
cat >> conf/local.conf << 'EOF'
|
|
MACHINE = \"qemux86-64\"
|
|
DISTRO = \"poky\"
|
|
|
|
# Rust support
|
|
ENABLE_RUST = \"1\"
|
|
|
|
# Performance optimizations
|
|
BB_NUMBER_THREADS = \"8\"
|
|
PARALLEL_MAKE = \"-j8\"
|
|
|
|
# Package management
|
|
PACKAGE_CLASSES = \"package_ipk\"
|
|
|
|
# SWUpdate integration
|
|
PREFERRED_PROVIDER_u-boot-fw-utils = \"libubootenv\"
|
|
IMAGE_FSTYPES += \"ext4.gz wic\"
|
|
|
|
# Add applications to rootfs
|
|
CORE_IMAGE_EXTRA_INSTALL += \"egui-kiosk swupdate swupdate-www\"
|
|
|
|
# Enable systemd
|
|
DISTRO_FEATURES_append = \" systemd\"
|
|
VIRTUAL-RUNTIME_init_manager = \"systemd\"
|
|
DISTRO_FEATURES_BACKFILL_CONSIDERED = \"sysvinit\"
|
|
VIRTUAL-RUNTIME_initscripts = \"\"
|
|
EOF
|
|
"
|
|
|
|
echo "Environment setup complete!"
|
|
echo "To enter the build environment, run: docker-compose exec yocto-builder bash"
|
|
```
|
|
|
|
### Complete Build Script
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# scripts/build-images.sh
|
|
|
|
set -e
|
|
|
|
echo "Building Yocto images..."
|
|
|
|
# Build in container
|
|
docker-compose exec yocto-builder bash -c "
|
|
cd /workspace/yocto &&
|
|
source sources/poky/oe-init-build-env build &&
|
|
|
|
# Build core system
|
|
echo 'Building minimal egui system...'
|
|
bitbake core-image-egui-minimal
|
|
|
|
# Build update image
|
|
echo 'Building SWUpdate package...'
|
|
bitbake update-image
|
|
|
|
# Build SDK for development
|
|
echo 'Building extensible SDK...'
|
|
bitbake core-image-egui-minimal -c populate_sdk_ext
|
|
|
|
echo 'Build completed successfully!'
|
|
echo 'Images available in: build/tmp/deploy/images/qemux86-64/'
|
|
"
|
|
|
|
# Test the built image
|
|
echo "Testing built image..."
|
|
./scripts/test-system.sh
|
|
|
|
echo "Build and test completed successfully!"
|
|
```
|
|
|
|
### Update Package Creation
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# scripts/create-update.sh
|
|
|
|
set -e
|
|
|
|
VERSION=${1:-$(date +%Y%m%d-%H%M%S)}
|
|
OUTPUT_DIR="updates"
|
|
|
|
echo "Creating SWUpdate package version: $VERSION"
|
|
|
|
mkdir -p $OUTPUT_DIR
|
|
|
|
# Build update in container
|
|
docker-compose exec yocto-builder bash -c "
|
|
cd /workspace/yocto &&
|
|
source sources/poky/oe-init-build-env build &&
|
|
|
|
# Update version in sw-description
|
|
sed -i 's/version = \".*\"/version = \"$VERSION\"/' \
|
|
/workspace/meta-layers/meta-custom/recipes-support/update-image/files/sw-description
|
|
|
|
# Build update package
|
|
bitbake update-image
|
|
|
|
# Copy to output directory
|
|
cp tmp/deploy/images/qemux86-64/update-image-qemux86-64.swu \
|
|
/workspace/$OUTPUT_DIR/system-update-$VERSION.swu
|
|
"
|
|
|
|
echo "Update package created: $OUTPUT_DIR/system-update-$VERSION.swu"
|
|
echo "Deploy with: swupdate -i $OUTPUT_DIR/system-update-$VERSION.swu"
|
|
```
|
|
|
|
## Production Deployment and Validation
|
|
|
|
This comprehensive system provides a **production-ready embedded Linux platform** with automatic updates, security hardening, and reliable operation. The containerized build approach ensures consistent results across development teams, while the A/B update system provides robust field update capabilities with automatic rollback protection.
|
|
|
|
**Key advantages of this architecture:**
|
|
- **Minimal host dependencies** through complete Docker containerization
|
|
- **Fast development cycles** with shared state caching and incremental builds
|
|
- **Reliable updates** with atomic A/B partition switching and automatic rollback
|
|
- **Optimized performance** with minimal Linux configuration and Rust applications
|
|
- **Comprehensive testing** with QEMU emulation and automated validation
|
|
|
|
The system scales from single-developer projects to large embedded teams, providing a solid foundation for Rust-based embedded GUI applications with modern DevOps practices and reliable over-the-air update capabilities. |