Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ffc26ac
wip: move cgo to separate process
ym Nov 11, 2025
bca9afd
first working POC
ym Nov 12, 2025
2f868bc
dead simple dual build target POC
ym Nov 12, 2025
db53e89
chore: add vscode configuration for c development
ym Nov 12, 2025
ac99991
feat: add gdbserver support for native code debugging
ym Nov 12, 2025
a494f2f
feat: add env utils
ym Nov 12, 2025
d6c97d1
fix: event handler
ym Nov 12, 2025
91d3b47
clean up code
ym Nov 13, 2025
afd8ab7
reconfigure display on native restart
ym Nov 13, 2025
6e9d846
feat: panic on max restart attempts
ym Nov 13, 2025
21641ff
chore: move generated methods to separate files
ym Nov 13, 2025
953b9de
feat: simplify failsafe mode for native proxy
ym Nov 13, 2025
b3a8d84
add a simple README for the native package
ym Nov 13, 2025
577424b
fix: event may be nil
ym Nov 13, 2025
470fcf4
fix: protect proxy methods from nil client
ym Nov 13, 2025
9c9c085
chore: move setStream to handleEventStream
ym Nov 18, 2025
64ec70d
feat: cancel all ongoing requests when closing the gRPC client
ym Nov 18, 2025
d84eb56
feat: allow to override max restart attempts
ym Nov 18, 2025
61dcd77
feat: add logger to restartProcess
ym Nov 18, 2025
6dee8a3
feat: add process ID to logger
ym Nov 18, 2025
9c0ac4e
fix: multiple goroutines issues
ym Nov 18, 2025
f3b854c
refactor(cgo-rpc): rewrite the monitor process logic
ym Nov 18, 2025
ddce624
fix: signal handling in the native process
ym Nov 18, 2025
9eccc22
chore: add README.md for native process debugging
ym Nov 18, 2025
7577867
Merge branch 'dev' into feat/cgo-rpc
ym Nov 19, 2025
a1f90da
fix
ym Nov 19, 2025
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
1 change: 1 addition & 0 deletions .devcontainer/install-deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ sudo apt-get install -y --no-install-recommends \
build-essential \
device-tree-compiler \
gperf g++-multilib gcc-multilib \
gdb-multiarch \
libnl-3-dev libdbus-1-dev libelf-dev libmpc-dev dwarves \
bc openssl flex bison libssl-dev python3 python-is-python3 texinfo kmod cmake \
wget zstd \
Expand Down
17 changes: 17 additions & 0 deletions .vscode/c_cpp_properties.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"compilerPath": "/opt/jetkvm-native-buildkit/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc",
"cStandard": "c17",
"cppStandard": "gnu++17",
"intelliSenseMode": "linux-gcc-arm",
"configurationProvider": "ms-vscode.cmake-tools"
}
],
"version": 4
}
28 changes: 28 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "GDB Debug - Native (binary)",
"type": "cppdbg",
"request": "launch",
"program": "internal/native/cgo/build/jknative-bin",
"args": [],
"stopAtEntry": true,
"cwd": "${workspaceFolder}",
"environment": [],
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb-multiarch",
"miDebuggerServerAddress": "${config:TARGET_IP}:${config:DEBUG_PORT}",
"targetArchitecture": "arm",
"preLaunchTask": "deploy",
"setupCommands": [
{
"description": "Pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"externalConsole": true
}
]
}
7 changes: 6 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@
]
},
"git.ignoreLimitWarning": true,
"cmake.sourceDirectory": "/workspaces/kvm-static-ip/internal/native/cgo",
"cmake.sourceDirectory": "${workspaceFolder}/internal/native/cgo",
"cmake.ignoreCMakeListsMissing": true,
"C_Cpp.inlayHints.autoDeclarationTypes.enabled": true,
"C_Cpp.inlayHints.parameterNames.enabled": true,
"C_Cpp.inlayHints.referenceOperator.enabled": true,
"TARGET_IP": "192.168.0.199",
"DEBUG_PORT": "2345",
"json.schemas": [
{
"fileMatch": [
Expand Down
30 changes: 30 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "deploy",
"isBackground": true,
"type": "shell",
"command": "bash",
"args": [
"dev_deploy.sh",
"-r",
"${config:TARGET_IP}",
"--gdb-port",
"${config:DEBUG_PORT}",
"--native-binary",
"--disable-docker"
],
"problemMatcher": {
"base": "$gcc",
"background": {
"activeOnStart": true,
"beginsPattern": "${config:BINARY}",
"endsPattern": "Listening on port [0-9]{4}"
}
}
},
]
}
6 changes: 6 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ rm /userdata/kvm_config.json
systemctl restart jetkvm
```

### Debug native code with gdbserver

Change the `TARGET_IP` in `.vscode/settings.json` to your JetKVM device IP, then set breakpoints in your native code and start the `Debug Native` configuration in VSCode.

The code and GDB server will be deployed automatically.

---

## Testing Your Changes
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ SKIP_NATIVE_IF_EXISTS ?= 0
SKIP_UI_BUILD ?= 0
ENABLE_SYNC_TRACE ?= 0

CMAKE_BUILD_TYPE ?= Release

GO_BUILD_ARGS := -tags netgo,timetzdata,nomsgpack
ifeq ($(ENABLE_SYNC_TRACE), 1)
GO_BUILD_ARGS := $(GO_BUILD_ARGS),synctrace
Expand Down Expand Up @@ -52,6 +54,7 @@ build_native:
echo "Building native..."; \
CC="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-gcc" \
LD="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-ld" \
CMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) \
./scripts/build_cgo.sh; \
fi

Expand Down
54 changes: 36 additions & 18 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,39 @@ import (

"github.com/erikdubbelboer/gspt"
"github.com/jetkvm/kvm"
"github.com/jetkvm/kvm/internal/native"
"github.com/jetkvm/kvm/internal/supervisor"
)

const (
envChildID = "JETKVM_CHILD_ID"
errorDumpDir = "/userdata/jetkvm/crashdump"
errorDumpLastFile = "last-crash.log"
errorDumpTemplate = "jetkvm-%s.log"
var (
subcomponent string
)

func program() {
gspt.SetProcTitle(os.Args[0] + " [app]")
kvm.Main()
subcomponentOverride := os.Getenv(supervisor.EnvSubcomponent)
if subcomponentOverride != "" {
subcomponent = subcomponentOverride
}
switch subcomponent {
case "native":
native.RunNativeProcess(os.Args[0])
default:
kvm.Main()
}
}

func setProcTitle(status string) {
if status != "" {
status = " " + status
}
title := fmt.Sprintf("jetkvm: [supervisor]%s", status)
gspt.SetProcTitle(title)
}

func main() {
versionPtr := flag.Bool("version", false, "print version and exit")
versionJSONPtr := flag.Bool("version-json", false, "print version as json and exit")
flag.StringVar(&subcomponent, "subcomponent", "", "subcomponent to run")
flag.Parse()

if *versionPtr || *versionJSONPtr {
Expand All @@ -42,7 +58,7 @@ func main() {
return
}

childID := os.Getenv(envChildID)
childID := os.Getenv(supervisor.EnvChildID)
switch childID {
case "":
doSupervise()
Expand All @@ -55,6 +71,8 @@ func main() {
}

func supervise() error {
setProcTitle("")

// check binary path
binPath, err := os.Executable()
if err != nil {
Expand All @@ -74,11 +92,11 @@ func supervise() error {
// run the child binary
cmd := exec.Command(binPath)

lastFilePath := filepath.Join(errorDumpDir, errorDumpLastFile)
lastFilePath := filepath.Join(supervisor.ErrorDumpDir, supervisor.ErrorDumpLastFile)

cmd.Env = append(os.Environ(), []string{
fmt.Sprintf("%s=%s", envChildID, kvm.GetBuiltAppVersion()),
fmt.Sprintf("JETKVM_LAST_ERROR_PATH=%s", lastFilePath),
fmt.Sprintf("%s=%s", supervisor.EnvChildID, kvm.GetBuiltAppVersion()),
fmt.Sprintf("%s=%s", supervisor.ErrorDumpLastFile, lastFilePath),
}...)
cmd.Args = os.Args

Expand All @@ -99,6 +117,8 @@ func supervise() error {
return fmt.Errorf("failed to start command: %w", startErr)
}

setProcTitle(fmt.Sprintf("started (pid=%d)", cmd.Process.Pid))

go func() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM)
Expand All @@ -107,8 +127,6 @@ func supervise() error {
_ = cmd.Process.Signal(sig)
}()

gspt.SetProcTitle(os.Args[0] + " [sup]")

cmdErr := cmd.Wait()
if cmdErr == nil {
return nil
Expand Down Expand Up @@ -186,11 +204,11 @@ func renameFile(f *os.File, newName string) error {

func ensureErrorDumpDir() error {
// TODO: check if the directory is writable
f, err := os.Stat(errorDumpDir)
f, err := os.Stat(supervisor.ErrorDumpDir)
if err == nil && f.IsDir() {
return nil
}
if err := os.MkdirAll(errorDumpDir, 0755); err != nil {
if err := os.MkdirAll(supervisor.ErrorDumpDir, 0755); err != nil {
return fmt.Errorf("failed to create error dump directory: %w", err)
}
return nil
Expand All @@ -200,7 +218,7 @@ func createErrorDump(logFile *os.File) {
fmt.Println()

fileName := fmt.Sprintf(
errorDumpTemplate,
supervisor.ErrorDumpTemplate,
time.Now().Format("20060102-150405"),
)

Expand All @@ -210,15 +228,15 @@ func createErrorDump(logFile *os.File) {
return
}

filePath := filepath.Join(errorDumpDir, fileName)
filePath := filepath.Join(supervisor.ErrorDumpDir, fileName)
if err := renameFile(logFile, filePath); err != nil {
fmt.Printf("failed to rename file: %v\n", err)
return
}

fmt.Printf("error dump copied: %s\n", filePath)

lastFilePath := filepath.Join(errorDumpDir, errorDumpLastFile)
lastFilePath := filepath.Join(supervisor.ErrorDumpDir, supervisor.ErrorDumpLastFile)

if err := ensureSymlink(filePath, lastFilePath); err != nil {
fmt.Printf("failed to create symlink: %v\n", err)
Expand Down
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ type Config struct {
DefaultLogLevel string `json:"default_log_level"`
VideoSleepAfterSec int `json:"video_sleep_after_sec"`
VideoQualityFactor float64 `json:"video_quality_factor"`
NativeMaxRestart uint `json:"native_max_restart_attempts"`
}

// GetUpdateAPIURL returns the update API URL
Expand Down
8 changes: 8 additions & 0 deletions display.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,14 @@ func updateStaticContents() {
// nativeInstance.UpdateLabelAndChangeVisibility("boot_screen_device_id", GetDeviceID())
}

// configureDisplayOnNativeRestart is called when the native process restarts
// it ensures the display is configured correctly after the restart
func configureDisplayOnNativeRestart() {
displayLogger.Info().Msg("native restarted, configuring display")
updateStaticContents()
requestDisplayUpdate(true, "native_restart")
}

// setDisplayBrightness sets /sys/class/backlight/backlight/brightness to alter
// the backlight brightness of the JetKVM hardware's display.
func setDisplayBrightness(brightness int, reason string) error {
Expand Down
4 changes: 3 additions & 1 deletion failsafe.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,12 @@ func checkFailsafeReason() {
_ = os.Remove(lastCrashPath)

// TODO: read the goroutine stack trace and check which goroutine is panicking
failsafeModeActive = true
if strings.Contains(failsafeCrashLog, "runtime.cgocall") {
failsafeModeActive = true
failsafeModeReason = "video"
return
} else {
failsafeModeReason = "unknown"
}
})
}
Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/caarlos0/env/v11 v11.3.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/creack/goselect v0.1.2 // indirect
Expand Down Expand Up @@ -87,6 +88,7 @@ require (
github.com/prometheus/client_model v0.6.2 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
Expand All @@ -97,6 +99,9 @@ require (
golang.org/x/oauth2 v0.32.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/text v0.30.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQ
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b h1:dSbDgy72Y1sjLPWLv7vs0fMFuhMBMViiT9PJZiZWZNs=
Expand Down Expand Up @@ -173,6 +175,8 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f h1:VgoRCP1efSCEZIcF2THLQ46+pIBzzgNiaUBe9wEDwYU=
github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f/go.mod h1:pzro7BGorij2WgrjEammtrkbo3+xldxo+KaGLGUiD+Q=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand Down Expand Up @@ -226,6 +230,12 @@ golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
20 changes: 20 additions & 0 deletions internal/native/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# jetkvm-native

This component (`internal/native/`) acts as a bridge between Golang and native (C/C++) code.
It manages spawning and communicating with a native process via sockets (gRPC and Unix stream).

For performance-critical operations such as video frame, **a dedicated Unix socket should be used** to avoid the overhead of gRPC and ensure low-latency communication.

## Debugging

To enable debug mode, create a file called `.native-debug-mode` in the `/userdata/jetkvm` directory.

```bash
touch /userdata/jetkvm/.native-debug-mode
```

This will cause the native process to listen for SIGHUP signal and crash the process.

```bash
pgrep native | xargs kill -SIGHUP
```
Loading