Add support for Luckfox PicoKVM

Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
This commit is contained in:
luckfox-eng29
2025-08-07 14:26:01 +08:00
parent 3e7d8fb0f5
commit 8fbd6bcf0d
114 changed files with 4676 additions and 3270 deletions

View File

@@ -97,7 +97,7 @@ func (l *Logger) updateLogLevel() {
finalDefaultLogLevel := l.defaultLogLevel
for name, level := range zerologLevels {
env := os.Getenv(fmt.Sprintf("JETKVM_LOG_%s", name))
env := os.Getenv(fmt.Sprintf("KVM_LOG_%s", name))
if env == "" {
env = os.Getenv(fmt.Sprintf("PION_LOG_%s", name))

View File

@@ -48,10 +48,17 @@ func (c pionLogger) Errorf(format string, args ...interface{}) {
type pionLoggerFactory struct{}
func (c pionLoggerFactory) NewLogger(subsystem string) logging.LeveledLogger {
logger := rootLogger.getLogger(subsystem).With().
var logger zerolog.Logger
base := rootLogger.getLogger(subsystem).With().
Str("scope", "pion").
Str("component", subsystem).
Logger()
Str("component", subsystem)
if subsystem == "mdns" {
logger = base.Logger().Level(zerolog.ErrorLevel) // 或 ErrorLevel
} else {
logger = base.Logger()
}
return pionLogger{logger: &logger}
}

View File

@@ -4,7 +4,7 @@ import "github.com/rs/zerolog"
var (
rootZerologLogger = zerolog.New(defaultLogOutput).With().
Str("scope", "jetkvm").
Str("scope", "kvm").
Timestamp().
Stack().
Logger()

View File

@@ -7,7 +7,8 @@ import (
"strings"
"sync"
"github.com/jetkvm/kvm/internal/logging"
"kvm/internal/logging"
pion_mdns "github.com/pion/mdns/v2"
"github.com/rs/zerolog"
"golang.org/x/net/ipv4"

View File

@@ -5,8 +5,9 @@ import (
"net"
"time"
"kvm/internal/mdns"
"github.com/guregu/null/v6"
"github.com/jetkvm/kvm/internal/mdns"
"golang.org/x/net/idna"
)

View File

@@ -5,9 +5,10 @@ import (
"net"
"sync"
"github.com/jetkvm/kvm/internal/confparser"
"github.com/jetkvm/kvm/internal/logging"
"github.com/jetkvm/kvm/internal/udhcpc"
"kvm/internal/confparser"
"kvm/internal/logging"
"kvm/internal/udhcpc"
"github.com/rs/zerolog"
"github.com/vishvananda/netlink"
@@ -58,7 +59,7 @@ func NewNetworkInterfaceState(opts *NetworkInterfaceOptions) (*NetworkInterfaceS
}
if opts.DefaultHostname == "" {
opts.DefaultHostname = "jetkvm"
opts.DefaultHostname = "picokvm"
}
err := confparser.SetDefaultsAndValidate(opts.NetworkConfig)

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"time"
"github.com/jetkvm/kvm/internal/confparser"
"github.com/jetkvm/kvm/internal/udhcpc"
"kvm/internal/confparser"
"kvm/internal/udhcpc"
)
type RpcIPv6Address struct {

View File

@@ -8,64 +8,64 @@ import (
var (
metricTimeSyncStatus = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "jetkvm_timesync_status",
Name: "kvm_timesync_status",
Help: "The status of the timesync, 1 if successful, 0 if not",
},
)
metricTimeSyncCount = promauto.NewCounter(
prometheus.CounterOpts{
Name: "jetkvm_timesync_total",
Name: "kvm_timesync_total",
Help: "The number of times the timesync has been run",
},
)
metricTimeSyncSuccessCount = promauto.NewCounter(
prometheus.CounterOpts{
Name: "jetkvm_timesync_success_total",
Name: "kvm_timesync_success_total",
Help: "The number of times the timesync has been successful",
},
)
metricRTCUpdateCount = promauto.NewCounter( //nolint:unused
prometheus.CounterOpts{
Name: "jetkvm_timesync_rtc_update_total",
Name: "kvm_timesync_rtc_update_total",
Help: "The number of times the RTC has been updated",
},
)
metricNtpTotalSuccessCount = promauto.NewCounter(
prometheus.CounterOpts{
Name: "jetkvm_timesync_ntp_total_success_total",
Name: "kvm_timesync_ntp_total_success_total",
Help: "The total number of successful NTP requests",
},
)
metricNtpTotalRequestCount = promauto.NewCounter(
prometheus.CounterOpts{
Name: "jetkvm_timesync_ntp_total_request_total",
Name: "kvm_timesync_ntp_total_request_total",
Help: "The total number of NTP requests sent",
},
)
metricNtpSuccessCount = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "jetkvm_timesync_ntp_success_total",
Name: "kvm_timesync_ntp_success_total",
Help: "The number of successful NTP requests",
},
[]string{"url"},
)
metricNtpRequestCount = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "jetkvm_timesync_ntp_request_total",
Name: "kvm_timesync_ntp_request_total",
Help: "The number of NTP requests sent to the server",
},
[]string{"url"},
)
metricNtpServerLastRTT = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "jetkvm_timesync_ntp_server_last_rtt",
Name: "kvm_timesync_ntp_server_last_rtt",
Help: "The last RTT of the NTP server in milliseconds",
},
[]string{"url"},
)
metricNtpServerRttHistogram = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "jetkvm_timesync_ntp_server_rtt",
Name: "kvm_timesync_ntp_server_rtt",
Help: "The histogram of the RTT of the NTP server in milliseconds",
Buckets: []float64{
10, 25, 50, 100, 200, 300, 500, 1000,
@@ -75,7 +75,7 @@ var (
)
metricNtpServerInfo = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "jetkvm_timesync_ntp_server_info",
Name: "kvm_timesync_ntp_server_info",
Help: "The info of the NTP server",
},
[]string{"url", "reference", "stratum", "precision"},
@@ -83,53 +83,53 @@ var (
metricHttpTotalSuccessCount = promauto.NewCounter(
prometheus.CounterOpts{
Name: "jetkvm_timesync_http_total_success_total",
Name: "kvm_timesync_http_total_success_total",
Help: "The total number of successful HTTP requests",
},
)
metricHttpTotalRequestCount = promauto.NewCounter(
prometheus.CounterOpts{
Name: "jetkvm_timesync_http_total_request_total",
Name: "kvm_timesync_http_total_request_total",
Help: "The total number of HTTP requests sent",
},
)
metricHttpTotalCancelCount = promauto.NewCounter(
prometheus.CounterOpts{
Name: "jetkvm_timesync_http_total_cancel_total",
Name: "kvm_timesync_http_total_cancel_total",
Help: "The total number of HTTP requests cancelled",
},
)
metricHttpSuccessCount = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "jetkvm_timesync_http_success_total",
Name: "kvm_timesync_http_success_total",
Help: "The number of successful HTTP requests",
},
[]string{"url"},
)
metricHttpRequestCount = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "jetkvm_timesync_http_request_total",
Name: "kvm_timesync_http_request_total",
Help: "The number of HTTP requests sent to the server",
},
[]string{"url"},
)
metricHttpCancelCount = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "jetkvm_timesync_http_cancel_total",
Name: "kvm_timesync_http_cancel_total",
Help: "The number of HTTP requests cancelled",
},
[]string{"url"},
)
metricHttpServerLastRTT = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "jetkvm_timesync_http_server_last_rtt",
Name: "kvm_timesync_http_server_last_rtt",
Help: "The last RTT of the HTTP server in milliseconds",
},
[]string{"url"},
)
metricHttpServerRttHistogram = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "jetkvm_timesync_http_server_rtt",
Name: "kvm_timesync_http_server_rtt",
Help: "The histogram of the RTT of the HTTP server in milliseconds",
Buckets: []float64{
10, 25, 50, 100, 200, 300, 500, 1000,
@@ -139,7 +139,7 @@ var (
)
metricHttpServerInfo = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "jetkvm_timesync_http_server_info",
Name: "kvm_timesync_http_server_info",
Help: "The info of the HTTP server",
},
[]string{"url", "http_code"},

View File

@@ -7,7 +7,8 @@ import (
"sync"
"time"
"github.com/jetkvm/kvm/internal/network"
"kvm/internal/network"
"github.com/rs/zerolog"
)

View File

@@ -4,12 +4,15 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"reflect"
"time"
"github.com/fsnotify/fsnotify"
"github.com/rs/zerolog"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
)
const (
@@ -66,6 +69,36 @@ func (c *DHCPClient) getWatchPaths() []string {
return paths
}
func (c *DHCPClient) watchLink() {
ch := make(chan netlink.LinkUpdate)
done := make(chan struct{})
if err := netlink.LinkSubscribe(ch, done); err != nil {
c.logger.Error().Err(err).Msg("failed to subscribe to netlink")
return
}
for update := range ch {
if update.Link.Attrs().Name == c.InterfaceName {
if update.IfInfomsg.Flags&unix.IFF_RUNNING != 0 {
fmt.Printf("[watchLink]link is up, starting udhcpc")
go c.runUDHCPC()
} else {
c.logger.Info().Msg("link is down")
}
}
}
}
func (c *DHCPClient) runUDHCPC() {
cmd := exec.Command("udhcpc", "-i", c.InterfaceName, "-t", "1")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
c.logger.Error().Err(err).Msg("failed to run udhcpc")
}
}
// Run starts the DHCP client and watches the lease file for changes.
// this isn't a blocking call, and the lease file is reloaded when a change is detected.
func (c *DHCPClient) Run() error {
@@ -80,6 +113,8 @@ func (c *DHCPClient) Run() error {
}
defer watcher.Close()
go c.watchLink()
go func() {
for {
select {

View File

@@ -15,7 +15,7 @@ var (
VendorId: "0x1d6b", //The Linux Foundation
ProductId: "0x0104", //Multifunction Composite Gadget
SerialNumber: "",
Manufacturer: "JetKVM",
Manufacturer: "KVM",
Product: "USB Emulation Device",
strictMode: true,
}
@@ -25,7 +25,7 @@ var (
Keyboard: true,
MassStorage: true,
}
usbGadgetName = "jetkvm"
usbGadgetName = "kvm"
usbGadget *UsbGadget
)
@@ -109,7 +109,7 @@ func TestUsbGadgetUDCNotBoundAfterReportDescrChanged(t *testing.T) {
udc := udcs[0]
assert.NotNil(udc, "UDC should exist")
udcStr, err := os.ReadFile("/sys/kernel/config/usb_gadget/jetkvm/UDC")
udcStr, err := os.ReadFile("/sys/kernel/config/usb_gadget/kvm/UDC")
assert.Nil(err, "usb_gadget/UDC should exist")
assert.Equal(strings.TrimSpace(udc), strings.TrimSpace(string(udcStr)), "UDC should be the same")
}

View File

@@ -34,7 +34,8 @@ var defaultGadgetConfig = map[string]gadgetConfigItem{
"bcdDevice": "0100",
},
configAttrs: gadgetAttributes{
"MaxPower": "250", // in unit of 2mA
"MaxPower": "250", // in unit of 2mA
"bmAttributes": "0xa0", // 0x80 = bus-powered, 0xa0 = bus-powered + remote wakeup
},
},
"base_info": {
@@ -43,8 +44,8 @@ var defaultGadgetConfig = map[string]gadgetConfigItem{
configPath: []string{"strings", "0x409"},
attrs: gadgetAttributes{
"serialnumber": "",
"manufacturer": "JetKVM",
"product": "JetKVM USB Emulation Device",
"manufacturer": "KVM",
"product": "KVM USB Emulation Device",
},
configAttrs: gadgetAttributes{
"configuration": "Config 1: HID",
@@ -59,6 +60,23 @@ var defaultGadgetConfig = map[string]gadgetConfigItem{
// mass storage
"mass_storage_base": massStorageBaseConfig,
"mass_storage_lun0": massStorageLun0Config,
// audio
"audio": {
order: 4000,
device: "uac1.usb0",
path: []string{"functions", "uac1.usb0"},
configPath: []string{"uac1.usb0"},
attrs: gadgetAttributes{
"p_chmask": "3",
"p_srate": "48000",
"p_ssize": "2",
"p_volume_present": "0",
"c_chmask": "3",
"c_srate": "48000",
"c_ssize": "2",
"c_volume_present": "0",
},
},
}
func (u *UsbGadget) isGadgetConfigItemEnabled(itemKey string) bool {
@@ -73,6 +91,8 @@ func (u *UsbGadget) isGadgetConfigItemEnabled(itemKey string) bool {
return u.enabledDevices.MassStorage
case "mass_storage_lun0":
return u.enabledDevices.MassStorage
case "audio":
return u.enabledDevices.Audio
default:
return true
}

View File

@@ -18,9 +18,8 @@ var massStorageLun0Config = gadgetConfigItem{
"ro": "1",
"removable": "1",
"file": "\n",
// the additional whitespace is intentional to avoid the "JetKVM V irtual Media" string
// https://github.com/jetkvm/rv1106-system/blob/778133a1c153041e73f7de86c9c434a2753ea65d/sysdrv/source/uboot/u-boot/drivers/usb/gadget/f_mass_storage.c#L2556
// the additional whitespace is intentional to avoid the "KVM V irtual Media" string
// Vendor (8 chars), product (16 chars)
"inquiry_string": "JetKVM Virtual Media",
"inquiry_string": "KVM Virtual Media",
},
}

View File

@@ -9,7 +9,8 @@ import (
"sync"
"time"
"github.com/jetkvm/kvm/internal/logging"
"kvm/internal/logging"
"github.com/rs/zerolog"
)
@@ -19,6 +20,7 @@ type Devices struct {
RelativeMouse bool `json:"relative_mouse"`
Keyboard bool `json:"keyboard"`
MassStorage bool `json:"mass_storage"`
Audio bool `json:"audio"`
}
// Config is a struct that represents the customizations for a USB gadget.

View File

@@ -1,55 +0,0 @@
package websecure
import (
"os"
"testing"
)
var (
fixtureEd25519Certificate = `-----BEGIN CERTIFICATE-----
MIIBQDCB86ADAgECAhQdB4qB6dV0/u1lwhJofQgkmjjV1zAFBgMrZXAwLzELMAkG
A1UEBhMCREUxIDAeBgNVBAMMF2VkMjU1MTktdGVzdC5qZXRrdm0uY29tMB4XDTI1
MDUyMzEyNTkyN1oXDTI3MDQyMzEyNTkyN1owLzELMAkGA1UEBhMCREUxIDAeBgNV
BAMMF2VkMjU1MTktdGVzdC5qZXRrdm0uY29tMCowBQYDK2VwAyEA9tLyoulJn7Ev
bf8kuD1ZGdA092773pCRjFEDKpXHonyjITAfMB0GA1UdDgQWBBRkmrVMfsLY57iy
r/0POP0S4QxCADAFBgMrZXADQQBfTRvqavLHDYQiKQTgbGod+Yn+fIq2lE584+1U
C4wh9peIJDFocLBEAYTQpEMKxa4s0AIRxD+a7aCS5oz0e/0I
-----END CERTIFICATE-----`
fixtureEd25519PrivateKey = `-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIKV08xUsLRHBfMXqZwxVRzIbViOp8G7aQGjPvoRFjujB
-----END PRIVATE KEY-----`
certStore *CertStore
certSigner *SelfSigner
)
func TestMain(m *testing.M) {
tlsStorePath, err := os.MkdirTemp("", "jktls.*")
if err != nil {
defaultLogger.Fatal().Err(err).Msg("failed to create temp directory")
}
certStore = NewCertStore(tlsStorePath, nil)
certStore.LoadCertificates()
certSigner = NewSelfSigner(
certStore,
nil,
"ci.jetkvm.com",
"JetKVM",
"JetKVM",
"JetKVM",
)
m.Run()
os.RemoveAll(tlsStorePath)
}
func TestSaveEd25519Certificate(t *testing.T) {
err, _ := certStore.ValidateAndSaveCertificate("ed25519-test.jetkvm.com", fixtureEd25519Certificate, fixtureEd25519PrivateKey, true)
if err != nil {
t.Fatalf("failed to save certificate: %v", err)
}
}