feat: Add device generation tools and validation workflow for TinyGo v0.39.0#25
Conversation
Migrate device generation tools and validation infrastructure from goplus/lib PR goplus#16 (goplus/lib#16) for TinyGo v0.39.0 adaptation. Changes: - Add setup-llgo GitHub Action for LLGO installation - Add device-gen.sh script for device package generation verification - Add validate_embed_packages.sh for device/machine validation - Add validate-embed-packages workflow for CI validation - Add gen-device-avr and gen-device-svd code generation tools - Update all import paths from github.com/goplus/lib/emb to github.com/goplus/emb These tools enable automated device package generation and validation for embedded targets using LLGO compiler. Source: goplus/lib PR goplus#16 (mv/task3 branch) Commit: ebe1263c33e1a05b18df7b1b3de2132a4a4e115d 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Summary of ChangesHello @luoliwoshang, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the project's embedded development infrastructure by introducing robust tools and workflows for automated device package generation and validation. It adapts the system for TinyGo v0.39.0, ensuring that device-specific code for various microcontrollers can be consistently generated, compiled, and kept in sync with upstream sources, thereby improving the reliability and maintainability of embedded targets. Highlights
Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive set of tools and CI workflows for device code generation and validation, migrated from another repository. The changes are well-organized and the scripts are quite detailed. My review focuses on improving the robustness and maintainability of the new tooling. I've identified several areas where error handling can be improved in both the shell scripts and Go generator tools to prevent silent failures and make the tools more resilient to unexpected inputs. Specifically, I've suggested changes to fail CI jobs on setup errors, handle filenames with spaces correctly, and replace panic calls in the Go tools with proper error returns.
| default: | ||
| panic("todo: unknown size") | ||
| } |
There was a problem hiding this comment.
The use of panic for an unhandled register size makes the tool brittle. If an ATDF file contains an unexpected register size, the entire tool will crash. It's better to handle this case by returning an error, which can then be gracefully handled by the caller. This makes the tool more robust and provides better feedback to the user.
case 2:
addReg(&Register{
Name: regEl.Name + "L",
Type: "volatile.Register8",
Caption: regEl.Caption + " (lower bits)",
Offset: regOffset + 0,
})
addReg(&Register{
Name: regEl.Name + "H",
Type: "volatile.Register8",
Caption: regEl.Caption + " (upper bits)",
Offset: regOffset + 1,
})
default:
return nil, fmt.Errorf("unsupported register size %d for register %s", regEl.Size, regEl.Name)| } | ||
| addr, err := strconv.ParseUint(*offsetString, 0, 32) | ||
| if err != nil { | ||
| panic(err) |
There was a problem hiding this comment.
This file contains numerous panic() calls for error handling (e.g., on lines 209, 267, 574, 736, 793, etc.). Using panic for recoverable errors like parsing issues makes the tool brittle and difficult to reuse as a library. It's idiomatic in Go to return errors and handle them gracefully in a higher-level function, like main or generate. This would likely require changing several function signatures to return an error type alongside their normal return values.
| # Set LLGO_ROOT to the source directory | ||
| echo "LLGO_ROOT=$PWD" >> $GITHUB_ENV | ||
| # Verify LLGO is available | ||
| llgo version || echo "Failed to get LLGO version" |
There was a problem hiding this comment.
| # Copy ignore list files to temporary directory | ||
| if [ -n "$ignore_list" ]; then | ||
| echo "[$name] Copying ignore list files to temporary directory: $ignore_list" | ||
| for file in $ignore_list; do |
There was a problem hiding this comment.
The ignore_list variable is treated as a space-separated string, which will cause issues if any of the filenames in the list contain spaces. It's more robust to define ignore_list as a bash array in the declare_*_config functions and iterate over it correctly.
For example, in declare_avr_config:
ignore_list=("avr.go" "avr_tiny85.go")And then in generate_devices, you should iterate over it like this:
for file in "${ignore_list[@]}"; doThis change should be applied to all declare_*_config functions and all loops that iterate over ignore_list (lines 237, 268, 279). Also, checks for non-empty list should be changed from if [ -n "$ignore_list" ] to if [ ${#ignore_list[@]} -gt 0 ] (lines 235, 261).
| // The derivedFrom attribute may also point to enumerated values | ||
| // in other registers and even peripherals, but this feature | ||
| // isn't often used in SVD files. | ||
| fmt.Fprintf(os.Stderr, "TODO: enumeratedValue.derivedFrom to a different register: %s\n", enumeratedValues.DerivedFrom) | ||
| } |
There was a problem hiding this comment.
This TODO indicates that deriving enumerated values from other registers is not fully implemented. While it currently prints a warning, this means the tool will not generate correct code for SVD files that use this feature. This should be implemented to ensure correctness for all supported SVD files.
|
|
||
| func main() { | ||
| indir := os.Args[1] // directory with register descriptor files (*.atdf) | ||
| outdir := os.Args[2] // output directory |
There was a problem hiding this comment.
Security: Path Traversal Vulnerability
Direct use of command-line arguments without validation could allow path traversal attacks (e.g., ../../etc/passwd).
Recommendation:
import "path/filepath"
func main() {
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "Usage: %s <input-dir> <output-dir>\n", os.Args[0])
os.Exit(1)
}
indir, err := filepath.Abs(os.Args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "Invalid input directory: %v\n", err)
os.Exit(1)
}
outdir, err := filepath.Abs(os.Args[2])
if err != nil {
fmt.Fprintf(os.Stderr, "Invalid output directory: %v\n", err)
os.Exit(1)
}| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer f.Close() |
There was a problem hiding this comment.
Security: XML External Entity (XXE) Vulnerability
XML decoder doesn't disable external entity processing, which could lead to information disclosure or SSRF if malicious ATDF files are processed.
Recommendation:
decoder := xml.NewDecoder(f)
decoder.Entity = xml.HTMLEntity // Limit to HTML entities only| } | ||
| indir := flag.Arg(0) | ||
| outdir := flag.Arg(1) | ||
| err := generate(indir, outdir, *sourceURL, *interruptSystem) |
There was a problem hiding this comment.
Security: Path Traversal Vulnerability
Same issue as gen-device-avr - command-line arguments should be validated and canonicalized before use to prevent path traversal attacks.
| } | ||
| defer f.Close() | ||
| decoder := xml.NewDecoder(f) | ||
| device := &SVDFile{} |
There was a problem hiding this comment.
Security: XML External Entity (XXE) Vulnerability
Same XXE vulnerability as in gen-device-avr. Disable external entity processing:
decoder := xml.NewDecoder(f)
decoder.Entity = xml.HTMLEntity| # Clone and setup repository | ||
| echo "[$name] Setting up repository..." | ||
| rm -rf "$lib_path" | ||
| git clone "$repo" "$lib_path" |
There was a problem hiding this comment.
Performance: Sequential Git Operations (Critical Bottleneck)
Git clone operations run sequentially for all 10 devices, wasting 100-600 seconds. Additionally, 7 devices share the same cmsis-svd-data repository but clone it separately (~350MB+ redundant transfers).
Recommendations:
- Use shallow clones:
git clone --depth 1 - Share repository clones between devices that use the same repo
- Parallelize device generation with background jobs
| echo "[$device_name] Processing ${#tasks_array[@]} tasks..." | ||
| for task in "${tasks_array[@]}"; do | ||
| echo "[$device_name] Processing task: $task" | ||
| "$generator" $task "$target_dir/" |
There was a problem hiding this comment.
Security: Potential Command Injection
The $task variable is expanded without proper quoting. While the current hardcoded values are safe, this pattern is risky.
Recommendation:
Use proper quoting or restructure to avoid word splitting:
"$generator" "$task" "$target_dir/"| done | ||
|
|
||
| # Remove all files from target directory | ||
| rm -rf "$target_dir"/* |
There was a problem hiding this comment.
Code Quality: Unsafe Glob Deletion
If $target_dir is empty or manipulated, this could delete unintended files.
Recommendation:
if [ -z "$target_dir" ] || [ "$target_dir" = "/" ]; then
echo "[$name] Error: Invalid target directory"
return 1
fi
rm -rf "${target_dir:?}"/*| wg.Done() | ||
| if err != nil { | ||
| // Store error to errChan if no error was stored before. | ||
| select { |
There was a problem hiding this comment.
Code Quality: Error Handling Issue
Only the first error is captured; subsequent errors are silently dropped. Also, wg.Done() should be called after error handling to ensure proper synchronization.
Recommendation:
var errMu sync.Mutex
var firstErr error
go func() {
for filepath := range workChan {
err := processFile(filepath, outdir)
if err != nil {
errMu.Lock()
if firstErr == nil {
firstErr = err
fmt.Fprintf(os.Stderr, "Error processing %s: %v\n", filepath, err)
}
errMu.Unlock()
}
wg.Done()
}
}()| @@ -0,0 +1,329 @@ | |||
| #!/bin/bash | |||
There was a problem hiding this comment.
Documentation: Missing Script Header
Add documentation explaining the script's purpose, usage, and modes:
#!/bin/bash
#
# device-gen.sh - Generate device files from upstream hardware description files
#
# Usage: ./device-gen.sh [generate|verify]
#
# Modes:
# generate - Clean target directories and regenerate all device files
# verify - Verify generated files match existing files (for CI)
# (default) - Generate without cleaning (preserves ignore list files)
#
set -e| @@ -0,0 +1,727 @@ | |||
| // From tinygo v0.39.0 | |||
| package main | |||
There was a problem hiding this comment.
Documentation: Missing Package Documentation
Add package-level documentation:
// gen-device-avr generates Go device packages from Atmel AVR ATDF descriptor files.
//
// This tool reads ATDF (Atmel Tools Device Files) XML files and generates:
// - Go register definitions
// - Interrupt vector definitions
// - Peripheral type definitions
//
// Usage:
// gen-device-avr <input-dir> <output-dir>
//
// The tool processes ATDF files in parallel using all available CPU cores.
package main| @@ -0,0 +1,1513 @@ | |||
| package main | |||
There was a problem hiding this comment.
Documentation: Missing Package Documentation
Add package-level documentation:
// gen-device-svd generates Go device packages from ARM CMSIS-SVD descriptor files.
//
// Usage:
// gen-device-svd [flags] <input-dir> <output-dir>
//
// Flags:
// -source string Source URL for SVD files (default "<unknown>")
// -interrupts string Interrupt system: "hardware" or "software" (default "hardware")
package main
Code Review SummaryThis PR adds valuable device generation infrastructure. The code is functional and well-structured, but several security vulnerabilities and performance bottlenecks need attention. Critical IssuesSecurity (High Priority):
Performance (70-75% potential improvement):
Additional ImprovementsDocumentation: Missing package-level docs and script usage instructions make maintenance harder. Error Handling: Race conditions in error channels, missing bounds checks, and panics instead of proper error returns. See inline comments for specific recommendations and remediation code. |
Change all device target paths from 'emb/device/*' to 'device/*' to match the goplus-emb repository structure, which differs from goplus/lib. Updated targets: - avr, esp, nrf, nxp, sam, sifive, kendryte, stm32, rp, renesas This fixes the verification failure where device-gen.sh could not find device directories during CI validation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Regenerate all device packages (avr, esp, nrf, nxp, sam, sifive, kendryte, stm32, rp, renesas) using the adapted device generation tools from goplus/lib PR goplus#16. Changes: - Update generated Go files with new import paths - Remove .ld and .s files (now handled by llgo) - Format all generated code with gofmt 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
ca2a687 to
bc3dbf6
Compare
Add //llgo:link directives to Asm and AsmFull functions for LLGO compiler compatibility. These functions are TinyGo compiler intrinsics that need adaptation for LLGO. Updated files: - device/asm.go - device/arm/arm.go - device/arm64/arm64.go - device/riscv/riscv.go - device/avr/avr.go 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
339c8d9 to
630a374
Compare
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Update import paths from TinyGo-style relative paths to full module paths: - "runtime/volatile" → "github.com/goplus/emb/runtime/volatile" - "device/arm" → "github.com/goplus/emb/device/arm" Updated files: - device/arm/arm.go - device/arm/scb.go - device/tkey/tkey.go - device/nxp/mimxrt1062_clock.go - device/nxp/mimxrt1062_mpu.go - device/gba/gba.go 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add panic-based placeholder implementations for ARM SVCall system call functions. These are TinyGo compiler intrinsics that need actual implementation in LLGO. Updated files: - device/arm/arm.go (SVCall0-4) - device/arm64/arm64.go (SVCall0-4) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add panic-based placeholder implementations for RISC-V CSR (Control and Status Register) functions. These are TinyGo compiler intrinsics that need actual implementation in LLGO. Updated file: - device/riscv/csr.go (Get, Set, SetBits, ClearBits) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Replace //export with //go:linkname for DisableInterrupts and EnableInterrupts functions in ARM. These will be linked to C implementations in llgo/targets/device/arm/interrupts.c. Updated file: - device/arm/arm.go 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Change AsmFull from llgo.asmfull to llgo.asm (signature determines behavior) - Add import _ "unsafe" to semihosting.go to match upstream 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
import "C" causes undefined symbol errors (SCS_BASE, Asm) when building with llgo. The C/assembly files are stored in LLGO at /llgo/targets/device/. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
These files are already stored in LLGO at /llgo/targets/device/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
aeb597c to
6f9e1e3
Compare
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
4658e5a to
b9a58f8
Compare
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
b9a58f8 to
ac4a8ef
Compare
Summary
This PR adapts the
device/directory to TinyGo v0.39.0 and implements LLGO compatibility. The device package contains two types of files:_tools/from upstream SVD/ATDF filesDevice Generation Tools
_tools/gen-device-avrdevice/avr/*.go_tools/gen-device-svddevice/{esp,nrf,nxp,sam,sifive,kendryte,stm32,rp,renesas}/*.goSync Workflow
Automated Generation Script
Manual Execution
Preserved Files (Not Overwritten by Generation Script)
The following files are manually maintained:
device/avr/avr.go,device/avr/avr_tiny85.godevice/nrf/README.markdowndevice/nxp/hardfault.go,device/nxp/mimxrt1062_*.godevice/sam/atsamd51x-bitfields.go,device/sam/atsame5x-bitfields.godevice/rp/rp2040-extra.go,device/rp/rp2350-extra.goLLGO Adaptation
TinyGo uses compiler intrinsics - functions without bodies that the compiler directly generates machine code for. LLGO doesn't recognize these intrinsics, so manual adaptation is required.
1. Inline Assembly Functions (
Asm/AsmFull)TinyGo syntax (no function body):
LLGO adaptation (using
//llgo:linkdirective):Affected files:
device/asm.godevice/arm/arm.godevice/arm64/arm64.godevice/riscv/riscv.godevice/avr/avr.go2. SVCall System Calls (ARM Cortex-M)
LLGO doesn't support these yet, using
panicplaceholder:Affected files:
device/arm/arm.go,device/arm64/arm64.go3. CSR Operations (RISC-V)
LLGO doesn't support these yet, using
panicplaceholder:Affected files:
device/riscv/csr.go(Get,Set,SetBits,ClearBits)4. Interrupt Control Functions
TinyGo syntax (using
//export):LLGO adaptation (using
//go:linkname):Affected files:
device/arm/arm.go5. Import Path Standardization
TinyGo uses relative paths, which need to be changed to full module paths:
"runtime/volatile""github.com/goplus/emb/runtime/volatile""device/arm""github.com/goplus/emb/device/arm"6. No
import "C"Requireddevice/arm/arm.gomust NOT containimport "C", otherwise it causes compilation errors:The C/assembly files are already stored in LLGO at
/llgo/targets/device/.Removed Redundant Files
The following
.cand.Sfiles have been removed as they are already in LLGO:device/arm/cortexm.Sdevice/arm/interrupts.cdevice/riscv/handleinterrupt.Sdevice/riscv/start.SDirectory Structure
CI/CD
.github/workflows/validate-embed-packages.ymlfor automated validation.github/actions/setup-llgofor LLGO environment setup2d80951e7d8f5823c1fa92c8a7cb5dd351a10e6aChange Statistics
.sassembly files)