Skip to content

Utilize GitHub Actions for building and running via QEMU #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .ci/run-qemu-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/bin/bash
set -e

# Check if at least one app is provided
if [ $# -eq 0 ]; then
echo "Usage: $0 <app1> [app2] [app3] ..."
echo "Example: $0 hello echo cpubench"
exit 1
fi

APPS="$@"
TIMEOUT=10
TOOLCHAIN_TYPE=${TOOLCHAIN_TYPE:-gnu}

echo "[+] Will run apps: $APPS"
echo "[+] Using toolchain: $TOOLCHAIN_TYPE"
echo ""

# Loop through each app
for app in $APPS; do
echo "=== Running $app ($TOOLCHAIN_TYPE) ==="

# Build the app
echo "[+] Building $app with $TOOLCHAIN_TYPE toolchain..."
make clean >/dev/null 2>&1
if ! make "$app" TOOLCHAIN_TYPE="$TOOLCHAIN_TYPE" >/dev/null 2>&1; then
echo "[!] Failed to build $app with $TOOLCHAIN_TYPE"
echo ""
continue
fi

# Run in QEMU with timeout
echo "[+] Running $app in QEMU (timeout: ${TIMEOUT}s)..."
set +e
timeout ${TIMEOUT}s qemu-system-riscv32 -nographic -machine virt -bios none -kernel build/image.elf
exit_code=$?
set -e

# Print result
if [ $exit_code -eq 124 ]; then
echo "[!] $app timed out"
elif [ $exit_code -eq 0 ]; then
echo "[✓] $app completed successfully"
else
echo "[!] $app failed with exit code $exit_code"
fi
echo ""
done

echo "[+] All apps tested with $TOOLCHAIN_TYPE toolchain"
60 changes: 60 additions & 0 deletions .ci/setup-toolchain.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/bin/bash
set -e

# Default to GNU if no toolchain specified
TOOLCHAIN_TYPE=${1:-gnu}

TOOLCHAIN_REPO=https://github.com/riscv-collab/riscv-gnu-toolchain
TOOLCHAIN_VERSION=2025.05.30
TOOLCHAIN_OS=ubuntu-24.04

setup_gnu_toolchain() {
echo "[+] Setting up GNU RISC-V toolchain..."

local URL="${TOOLCHAIN_REPO}/releases/download/${TOOLCHAIN_VERSION}/riscv32-elf-${TOOLCHAIN_OS}-gcc-nightly-${TOOLCHAIN_VERSION}-nightly.tar.xz"

echo "[+] Downloading RISC-V GNU toolchain..."
wget -q "$URL"
tar -xf "$(basename "$URL")"

echo "[+] Exporting GNU toolchain path..."
echo "$PWD/riscv/bin" >> "$GITHUB_PATH"

# Set cross-compile prefix for GNU
echo "CROSS_COMPILE=riscv32-unknown-elf-" >> "$GITHUB_ENV"
echo "TOOLCHAIN_TYPE=gnu" >> "$GITHUB_ENV"
}

setup_llvm_toolchain() {
echo "[+] Setting up LLVM RISC-V toolchain..."

# upstream URL for LLVM toolchainzz2
local URL="${TOOLCHAIN_REPO}/releases/download/${TOOLCHAIN_VERSION}/riscv32-elf-${TOOLCHAIN_OS}-llvm-nightly-${TOOLCHAIN_VERSION}-nightly.tar.xz"

echo "[+] Downloading RISC-V LLVM toolchain..."
wget -q "$URL"
tar -xf "$(basename "$URL")"

echo "[+] Exporting LLVM toolchain path..."
echo "$PWD/riscv/bin" >> "$GITHUB_PATH"

# Set cross-compile prefix for LLVM
echo "CROSS_COMPILE=riscv32-unknown-elf-" >> "$GITHUB_ENV"
echo "TOOLCHAIN_TYPE=llvm" >> "$GITHUB_ENV"
}

case "$TOOLCHAIN_TYPE" in
"gnu")
setup_gnu_toolchain
;;
"llvm")
setup_llvm_toolchain
;;
*)
echo "Error: Unknown toolchain type '$TOOLCHAIN_TYPE'"
echo "Usage: $0 [gnu|llvm]"
exit 1
;;
esac

echo "[+] Toolchain setup complete: $TOOLCHAIN_TYPE"
85 changes: 85 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Linmo CI

on:
push:
pull_request:

jobs:
matrix-tests:
runs-on: ubuntu-24.04
name: Test on ${{ matrix.toolchain }} toolchain

strategy:
fail-fast: false
matrix:
toolchain: [gnu, llvm]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install base dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential qemu-system-riscv32 wget

- name: Setup ${{ matrix.toolchain }} toolchain
run: .ci/setup-toolchain.sh ${{ matrix.toolchain }}

- name: Verify toolchain installation
run: |
if [ "${{ matrix.toolchain }}" = "gnu" ]; then
riscv32-unknown-elf-gcc --version
else
# LLVM toolchain fallback: try system llvm-objdump
riscv32-unknown-elf-clang --version || clang --version
riscv32-unknown-elf-llvm-objdump --version || llvm-objdump --version
fi
qemu-system-riscv32 --version

- name: Build Kernel
run: |
make clean
make
env:
TOOLCHAIN_TYPE: ${{ matrix.toolchain }}

- name: Run Basic Apps
id: test
run: |
output=$(.ci/run-qemu-tests.sh cpubench )
echo "TEST_OUTPUT<<EOF" >> $GITHUB_OUTPUT
echo "$output" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
env:
TOOLCHAIN_TYPE: ${{ matrix.toolchain }}

- name: Comment PR with results
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const output = `${{ steps.test.outputs.TEST_OUTPUT }}`;
const toolchain = `${{ matrix.toolchain }}`.toUpperCase();
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Linmo QEMU App Test Result (${toolchain} Toolchain)\n\n\`\`\`\n${output}\n\`\`\`\n\n_This is an automated report from CI._`
});

# Optional: Create a summary job that depends on all matrix jobs
test-summary:
runs-on: ubuntu-24.04
needs: matrix-tests
if: always()

steps:
- name: Check test results
run: |
if [ "${{ needs.matrix-tests.result }}" = "success" ]; then
echo "✅ All toolchain tests passed!"
else
echo "❌ Some toolchain tests failed"
exit 1
fi
57 changes: 42 additions & 15 deletions arch/riscv/build.mk
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,54 @@ DEFINES := -DF_CPU=$(F_CLK) \
-DF_TIMER=$(F_TICK) \
-include config.h

ASFLAGS = -march=rv32imzicsr -mabi=ilp32
CROSS_COMPILE ?= riscv32-unknown-elf-
CC = $(CROSS_COMPILE)gcc
CC_IS_CLANG := $(shell $(CC) --version 2>/dev/null | grep -qi clang && echo 1)

# Architecture flags
ARCH_FLAGS = -march=rv32imzicsr -mabi=ilp32

# Common compiler flags
CFLAGS += -Wall -Wextra -Wshadow -Wno-unused-parameter -Werror
CFLAGS += -O2 -std=gnu99
CFLAGS += -march=rv32imzicsr -mabi=ilp32
CFLAGS += $(ARCH_FLAGS)
CFLAGS += -mstrict-align -ffreestanding -nostdlib -fomit-frame-pointer
CFLAGS += $(INC_DIRS) $(DEFINES) -fdata-sections -ffunction-sections
ARFLAGS = r

# Linker flags
LDFLAGS = -melf32lriscv --gc-sections
LDSCRIPT = $(ARCH_DIR)/riscv32-qemu.ld
ifeq ($(CC_IS_CLANG),1)
ifeq ($(TOOLCHAIN_TYPE),llvm)
CC = $(CROSS_COMPILE)clang
AS = $(CROSS_COMPILE)clang
LD = $(CROSS_COMPILE)ld.lld
DUMP = $(CROSS_COMPILE)llvm-objdump -M no-aliases
READ = $(CROSS_COMPILE)llvm-readelf
OBJ = $(CROSS_COMPILE)llvm-objcopy
SIZE = $(CROSS_COMPILE)llvm-size
AR = $(CROSS_COMPILE)ar

CFLAGS += --target=riscv32-unknown-elf
CFLAGS += -Wno-unused-command-line-argument
ASFLAGS = --target=riscv32-unknown-elf $(ARCH_FLAGS)
LDFLAGS = -m elf32lriscv --gc-sections
else
CC = $(CC_DEFAULT)
CC = $(CROSS_COMPILE)gcc
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
DUMP = $(CROSS_COMPILE)objdump -Mno-aliases
READ = $(CROSS_COMPILE)readelf
OBJ = $(CROSS_COMPILE)objcopy
SIZE = $(CROSS_COMPILE)size
AR = $(CROSS_COMPILE)ar

CROSS_COMPILE ?= riscv-none-elf-
CC = $(CROSS_COMPILE)gcc
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
DUMP = $(CROSS_COMPILE)objdump -Mno-aliases
READ = $(CROSS_COMPILE)readelf
OBJ = $(CROSS_COMPILE)objcopy
SIZE = $(CROSS_COMPILE)size
AR = $(CROSS_COMPILE)ar
ASFLAGS = $(ARCH_FLAGS)
LDFLAGS = -melf32lriscv --gc-sections
endif

AR = $(CROSS_COMPILE)ar

ARFLAGS = r
LDSCRIPT = $(ARCH_DIR)/riscv32-qemu.ld

HAL_OBJS := boot.o hal.o muldiv.o
HAL_OBJS := $(addprefix $(BUILD_KERNEL_DIR)/,$(HAL_OBJS))
Expand Down
6 changes: 4 additions & 2 deletions kernel/pipe.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#define PIPE_MIN_SIZE 4
#define PIPE_MAX_SIZE 32768

#define __maybe_unused __attribute__((__unused__))

/* Enhanced validation with comprehensive integrity checks */
static inline bool pipe_is_valid(const pipe_t *p)
{
Expand All @@ -34,15 +36,15 @@ static inline uint16_t pipe_free_space_internal(const pipe_t *p)
return (p->mask + 1) - p->used;
}

static inline char pipe_get_byte(pipe_t *p)
static inline __attribute__((__unused__)) char pipe_get_byte(pipe_t *p)
{
char val = p->buf[p->head];
p->head = (p->head + 1) & p->mask;
p->used--;
return val;
}

static inline void pipe_put_byte(pipe_t *p, char c)
static inline __attribute__((__unused__)) void pipe_put_byte(pipe_t *p, char c)
{
p->buf[p->tail] = c;
p->tail = (p->tail + 1) & p->mask;
Expand Down
2 changes: 0 additions & 2 deletions lib/malloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,13 @@ void free(void *ptr)
static void selective_coalesce(void)
{
memblock_t *p = first_free;
uint32_t coalesced = 0;

while (p && p->next) {
/* Merge only when blocks are FREE *and* adjacent in memory */
uint8_t *pend = (uint8_t *) p + sizeof(memblock_t) + GET_SIZE(p);
if (!IS_USED(p) && !IS_USED(p->next) && pend == (uint8_t *) p->next) {
p->size = GET_SIZE(p) + sizeof(memblock_t) + GET_SIZE(p->next);
p->next = p->next->next;
coalesced++;
free_blocks_count--;
} else {
p = p->next;
Expand Down