diff --git a/.github/update.log b/.github/update.log
index 0a497123d8..6b3874b2a0 100644
--- a/.github/update.log
+++ b/.github/update.log
@@ -976,3 +976,4 @@ Update On Mon Apr 14 20:39:52 CEST 2025
Update On Tue Apr 15 20:36:26 CEST 2025
Update On Wed Apr 16 20:40:48 CEST 2025
Update On Thu Apr 17 20:38:25 CEST 2025
+Update On Fri Apr 18 20:35:44 CEST 2025
diff --git a/clash-meta/.github/workflows/test.yml b/clash-meta/.github/workflows/test.yml
index def99d9373..db7ac0373e 100644
--- a/clash-meta/.github/workflows/test.yml
+++ b/clash-meta/.github/workflows/test.yml
@@ -37,6 +37,8 @@ jobs:
env:
CGO_ENABLED: 0
GOTOOLCHAIN: local
+ # Fix mingw trying to be smart and converting paths https://github.com/moby/moby/issues/24029#issuecomment-250412919
+ MSYS_NO_PATHCONV: true
steps:
- uses: actions/checkout@v4
diff --git a/clash-meta/adapter/outbound/anytls.go b/clash-meta/adapter/outbound/anytls.go
index 4727b1887f..7fb1d9c646 100644
--- a/clash-meta/adapter/outbound/anytls.go
+++ b/clash-meta/adapter/outbound/anytls.go
@@ -11,7 +11,6 @@ import (
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
- tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/anytls"
"github.com/metacubex/mihomo/transport/vmess"
@@ -115,9 +114,6 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
if tlsConfig.Host == "" {
tlsConfig.Host = option.Server
}
- if tlsC.HaveGlobalFingerprint() && len(option.ClientFingerprint) == 0 {
- tlsConfig.ClientFingerprint = tlsC.GetGlobalFingerprint()
- }
tOption.TLSConfig = tlsConfig
outbound := &AnyTLS{
diff --git a/clash-meta/adapter/outbound/trojan.go b/clash-meta/adapter/outbound/trojan.go
index 241666e52d..d6ca43794c 100644
--- a/clash-meta/adapter/outbound/trojan.go
+++ b/clash-meta/adapter/outbound/trojan.go
@@ -18,12 +18,13 @@ import (
"github.com/metacubex/mihomo/transport/gun"
"github.com/metacubex/mihomo/transport/shadowsocks/core"
"github.com/metacubex/mihomo/transport/trojan"
+ "github.com/metacubex/mihomo/transport/vmess"
)
type Trojan struct {
*Base
- instance *trojan.Trojan
- option *TrojanOption
+ option *TrojanOption
+ hexPassword [trojan.KeyLength]byte
// for gun mux
gunTLSConfig *tls.Config
@@ -61,15 +62,21 @@ type TrojanSSOption struct {
Password string `proxy:"password,omitempty"`
}
-func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) {
- if t.option.Network == "ws" {
+// StreamConnContext implements C.ProxyAdapter
+func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
+ switch t.option.Network {
+ case "ws":
host, port, _ := net.SplitHostPort(t.addr)
- wsOpts := &trojan.WebsocketOption{
+
+ wsOpts := &vmess.WebsocketConfig{
Host: host,
Port: port,
Path: t.option.WSOpts.Path,
+ MaxEarlyData: t.option.WSOpts.MaxEarlyData,
+ EarlyDataHeaderName: t.option.WSOpts.EarlyDataHeaderName,
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen,
+ ClientFingerprint: t.option.ClientFingerprint,
Headers: http.Header{},
}
@@ -83,35 +90,64 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error)
}
}
- return t.instance.StreamWebsocketConn(ctx, c, wsOpts)
- }
+ alpn := trojan.DefaultWebsocketALPN
+ if len(t.option.ALPN) != 0 {
+ alpn = t.option.ALPN
+ }
- return t.instance.StreamConn(ctx, c)
-}
+ wsOpts.TLS = true
+ tlsConfig := &tls.Config{
+ NextProtos: alpn,
+ MinVersion: tls.VersionTLS12,
+ InsecureSkipVerify: t.option.SkipCertVerify,
+ ServerName: t.option.SNI,
+ }
-// StreamConnContext implements C.ProxyAdapter
-func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
- var err error
+ wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint)
+ if err != nil {
+ return nil, err
+ }
- if tlsC.HaveGlobalFingerprint() && len(t.option.ClientFingerprint) == 0 {
- t.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
- }
-
- if t.transport != nil {
+ c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
+ case "grpc":
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig)
- } else {
- c, err = t.plainStream(ctx, c)
+ default:
+ // default tcp network
+ // handle TLS
+ alpn := trojan.DefaultALPN
+ if len(t.option.ALPN) != 0 {
+ alpn = t.option.ALPN
+ }
+ c, err = vmess.StreamTLSConn(ctx, c, &vmess.TLSConfig{
+ Host: t.option.SNI,
+ SkipCertVerify: t.option.SkipCertVerify,
+ FingerPrint: t.option.Fingerprint,
+ ClientFingerprint: t.option.ClientFingerprint,
+ NextProtos: alpn,
+ Reality: t.realityConfig,
+ })
}
-
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
+ return t.streamConnContext(ctx, c, metadata)
+}
+
+func (t *Trojan) streamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
- err = t.writeHeaderContext(ctx, c, metadata)
+ if ctx.Done() != nil {
+ done := N.SetupContextForConn(ctx, c)
+ defer done(&err)
+ }
+ command := trojan.CommandTCP
+ if metadata.NetWork == C.UDP {
+ command = trojan.CommandUDP
+ }
+ err = trojan.WriteHeader(c, t.hexPassword, command, serializesSocksAddr(metadata))
return c, err
}
@@ -124,25 +160,25 @@ func (t *Trojan) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C
if metadata.NetWork == C.UDP {
command = trojan.CommandUDP
}
- err = t.instance.WriteHeader(c, command, serializesSocksAddr(metadata))
+ err = trojan.WriteHeader(c, t.hexPassword, command, serializesSocksAddr(metadata))
return err
}
// DialContext implements C.ProxyAdapter
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
+ var c net.Conn
// gun transport
if t.transport != nil && dialer.IsZeroOptions(opts) {
- c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig)
+ c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
if err != nil {
return nil, err
}
+ defer func(c net.Conn) {
+ safeConnClose(c, err)
+ }(c)
- if t.ssCipher != nil {
- c = t.ssCipher.StreamConn(c)
- }
-
- if err = t.writeHeaderContext(ctx, c, metadata); err != nil {
- c.Close()
+ c, err = t.streamConnContext(ctx, c, metadata)
+ if err != nil {
return nil, err
}
@@ -190,16 +226,12 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
safeConnClose(c, err)
}(c)
- if t.ssCipher != nil {
- c = t.ssCipher.StreamConn(c)
- }
-
- err = t.writeHeaderContext(ctx, c, metadata)
+ c, err = t.streamConnContext(ctx, c, metadata)
if err != nil {
return nil, err
}
- pc := t.instance.PacketConn(c)
+ pc := trojan.NewPacketConn(c)
return newPacketConn(pc, t), err
}
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
@@ -220,21 +252,12 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
- c, err = t.plainStream(ctx, c)
- if err != nil {
- return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
- }
-
- if t.ssCipher != nil {
- c = t.ssCipher.StreamConn(c)
- }
-
- err = t.writeHeaderContext(ctx, c, metadata)
+ c, err = t.StreamConnContext(ctx, c, metadata)
if err != nil {
return nil, err
}
- pc := t.instance.PacketConn(c)
+ pc := trojan.NewPacketConn(c)
return newPacketConn(pc, t), err
}
@@ -245,7 +268,7 @@ func (t *Trojan) SupportWithDialer() C.NetWork {
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (t *Trojan) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
- pc := t.instance.PacketConn(c)
+ pc := trojan.NewPacketConn(c)
return newPacketConn(pc, t), err
}
@@ -272,19 +295,6 @@ func (t *Trojan) Close() error {
func NewTrojan(option TrojanOption) (*Trojan, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
- tOption := &trojan.Option{
- Password: option.Password,
- ALPN: option.ALPN,
- ServerName: option.Server,
- SkipCertVerify: option.SkipCertVerify,
- Fingerprint: option.Fingerprint,
- ClientFingerprint: option.ClientFingerprint,
- }
-
- if option.SNI != "" {
- tOption.ServerName = option.SNI
- }
-
t := &Trojan{
Base: &Base{
name: option.Name,
@@ -297,8 +307,8 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
- instance: trojan.New(tOption),
- option: &option,
+ option: &option,
+ hexPassword: trojan.Key(option.Password),
}
var err error
@@ -306,7 +316,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
if err != nil {
return nil, err
}
- tOption.Reality = t.realityConfig
if option.SSOpts.Enabled {
if option.SSOpts.Password == "" {
@@ -342,8 +351,8 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
tlsConfig := &tls.Config{
NextProtos: option.ALPN,
MinVersion: tls.VersionTLS12,
- InsecureSkipVerify: tOption.SkipCertVerify,
- ServerName: tOption.ServerName,
+ InsecureSkipVerify: option.SkipCertVerify,
+ ServerName: option.SNI,
}
var err error
@@ -352,13 +361,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
return nil, err
}
- t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig)
+ t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, option.ClientFingerprint, t.realityConfig)
t.gunTLSConfig = tlsConfig
t.gunConfig = &gun.Config{
ServiceName: option.GrpcOpts.GrpcServiceName,
- Host: tOption.ServerName,
- ClientFingerprint: tOption.ClientFingerprint,
+ Host: option.SNI,
+ ClientFingerprint: option.ClientFingerprint,
}
}
diff --git a/clash-meta/adapter/outbound/vless.go b/clash-meta/adapter/outbound/vless.go
index 079d7bc274..4d1a23b8e1 100644
--- a/clash-meta/adapter/outbound/vless.go
+++ b/clash-meta/adapter/outbound/vless.go
@@ -75,13 +75,7 @@ type VlessOption struct {
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
}
-func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
- var err error
-
- if tlsC.HaveGlobalFingerprint() && len(v.option.ClientFingerprint) == 0 {
- v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
- }
-
+func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
switch v.option.Network {
case "ws":
host, port, _ := net.SplitHostPort(v.addr)
@@ -232,9 +226,10 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
// DialContext implements C.ProxyAdapter
func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
+ var c net.Conn
// gun transport
if v.transport != nil && dialer.IsZeroOptions(opts) {
- c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
+ c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
diff --git a/clash-meta/adapter/outbound/vmess.go b/clash-meta/adapter/outbound/vmess.go
index 4db0ceddb5..fddef0e1b1 100644
--- a/clash-meta/adapter/outbound/vmess.go
+++ b/clash-meta/adapter/outbound/vmess.go
@@ -96,13 +96,7 @@ type WSOptions struct {
}
// StreamConnContext implements C.ProxyAdapter
-func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
- var err error
-
- if tlsC.HaveGlobalFingerprint() && (len(v.option.ClientFingerprint) == 0) {
- v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
- }
-
+func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
switch v.option.Network {
case "ws":
host, port, _ := net.SplitHostPort(v.addr)
@@ -226,10 +220,10 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
if err != nil {
return nil, err
}
- return v.streamConnConntext(ctx, c, metadata)
+ return v.streamConnContext(ctx, c, metadata)
}
-func (v *Vmess) streamConnConntext(ctx context.Context, c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
+func (v *Vmess) streamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
useEarly := N.NeedHandshake(c)
if !useEarly {
if ctx.Done() != nil {
@@ -287,9 +281,10 @@ func (v *Vmess) streamConnConntext(ctx context.Context, c net.Conn, metadata *C.
// DialContext implements C.ProxyAdapter
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
+ var c net.Conn
// gun transport
if v.transport != nil && dialer.IsZeroOptions(opts) {
- c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
+ c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
@@ -297,7 +292,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
safeConnClose(c, err)
}(c)
- c, err = v.streamConnConntext(ctx, c, metadata)
+ c, err = v.streamConnContext(ctx, c, metadata)
if err != nil {
return nil, err
}
@@ -348,7 +343,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
safeConnClose(c, err)
}(c)
- c, err = v.streamConnConntext(ctx, c, metadata)
+ c, err = v.streamConnContext(ctx, c, metadata)
if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err)
}
diff --git a/clash-meta/adapter/parser.go b/clash-meta/adapter/parser.go
index 48359f7052..56febe2817 100644
--- a/clash-meta/adapter/parser.go
+++ b/clash-meta/adapter/parser.go
@@ -5,7 +5,6 @@ import (
"github.com/metacubex/mihomo/adapter/outbound"
"github.com/metacubex/mihomo/common/structure"
- tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
)
@@ -22,7 +21,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
)
switch proxyType {
case "ss":
- ssOption := &outbound.ShadowSocksOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
+ ssOption := &outbound.ShadowSocksOption{}
err = decoder.Decode(mapping, ssOption)
if err != nil {
break
@@ -55,7 +54,6 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
Method: "GET",
Path: []string{"/"},
},
- ClientFingerprint: tlsC.GetGlobalFingerprint(),
}
err = decoder.Decode(mapping, vmessOption)
@@ -64,7 +62,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
}
proxy, err = outbound.NewVmess(*vmessOption)
case "vless":
- vlessOption := &outbound.VlessOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
+ vlessOption := &outbound.VlessOption{}
err = decoder.Decode(mapping, vlessOption)
if err != nil {
break
@@ -78,7 +76,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
}
proxy, err = outbound.NewSnell(*snellOption)
case "trojan":
- trojanOption := &outbound.TrojanOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
+ trojanOption := &outbound.TrojanOption{}
err = decoder.Decode(mapping, trojanOption)
if err != nil {
break
diff --git a/clash-meta/component/dialer/dialer.go b/clash-meta/component/dialer/dialer.go
index 6e1f242661..d7e7072aa5 100644
--- a/clash-meta/component/dialer/dialer.go
+++ b/clash-meta/component/dialer/dialer.go
@@ -20,34 +20,16 @@ const (
DefaultUDPTimeout = DefaultTCPTimeout
)
-type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error)
+type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error)
var (
dialMux sync.Mutex
- IP4PEnable bool
actualSingleStackDialContext = serialSingleStackDialContext
actualDualStackDialContext = serialDualStackDialContext
tcpConcurrent = false
fallbackTimeout = 300 * time.Millisecond
)
-func applyOptions(options ...Option) *option {
- opt := &option{
- interfaceName: DefaultInterface.Load(),
- routingMark: int(DefaultRoutingMark.Load()),
- }
-
- for _, o := range DefaultOptions {
- o(opt)
- }
-
- for _, o := range options {
- o(opt)
- }
-
- return opt
-}
-
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
opt := applyOptions(options...)
@@ -77,38 +59,43 @@ func DialContext(ctx context.Context, network, address string, options ...Option
}
func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort, options ...Option) (net.PacketConn, error) {
- cfg := applyOptions(options...)
+ opt := applyOptions(options...)
lc := &net.ListenConfig{}
- if cfg.addrReuse {
+ if opt.addrReuse {
addrReuseToListenConfig(lc)
}
if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CMFA)
socketHookToListenConfig(lc)
} else {
- interfaceName := cfg.interfaceName
- if interfaceName == "" {
+ if opt.interfaceName == "" {
+ opt.interfaceName = DefaultInterface.Load()
+ }
+ if opt.interfaceName == "" {
if finder := DefaultInterfaceFinder.Load(); finder != nil {
- interfaceName = finder.FindInterfaceName(rAddrPort.Addr())
+ opt.interfaceName = finder.FindInterfaceName(rAddrPort.Addr())
}
}
if rAddrPort.Addr().Unmap().IsLoopback() {
// avoid "The requested address is not valid in its context."
- interfaceName = ""
+ opt.interfaceName = ""
}
- if interfaceName != "" {
+ if opt.interfaceName != "" {
bind := bindIfaceToListenConfig
- if cfg.fallbackBind {
+ if opt.fallbackBind {
bind = fallbackBindIfaceToListenConfig
}
- addr, err := bind(interfaceName, lc, network, address, rAddrPort)
+ addr, err := bind(opt.interfaceName, lc, network, address, rAddrPort)
if err != nil {
return nil, err
}
address = addr
}
- if cfg.routingMark != 0 {
- bindMarkToListenConfig(cfg.routingMark, lc, network, address)
+ if opt.routingMark == 0 {
+ opt.routingMark = int(DefaultRoutingMark.Load())
+ }
+ if opt.routingMark != 0 {
+ bindMarkToListenConfig(opt.routingMark, lc, network, address)
}
}
@@ -134,7 +121,7 @@ func GetTcpConcurrent() bool {
return tcpConcurrent
}
-func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
+func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt option) (net.Conn, error) {
var address string
destination, port = resolver.LookupIP4P(destination, port)
address = net.JoinHostPort(destination.String(), port)
@@ -159,21 +146,26 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CMFA)
socketHookToToDialer(dialer)
} else {
- interfaceName := opt.interfaceName // don't change the "opt", it's a pointer
- if interfaceName == "" {
+ if opt.interfaceName == "" {
+ opt.interfaceName = DefaultInterface.Load()
+ }
+ if opt.interfaceName == "" {
if finder := DefaultInterfaceFinder.Load(); finder != nil {
- interfaceName = finder.FindInterfaceName(destination)
+ opt.interfaceName = finder.FindInterfaceName(destination)
}
}
- if interfaceName != "" {
+ if opt.interfaceName != "" {
bind := bindIfaceToDialer
if opt.fallbackBind {
bind = fallbackBindIfaceToDialer
}
- if err := bind(interfaceName, dialer, network, destination); err != nil {
+ if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
return nil, err
}
}
+ if opt.routingMark == 0 {
+ opt.routingMark = int(DefaultRoutingMark.Load())
+ }
if opt.routingMark != 0 {
bindMarkToDialer(opt.routingMark, dialer, network, destination)
}
@@ -185,26 +177,26 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
return dialer.DialContext(ctx, network, address)
}
-func serialSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
+func serialSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
return serialDialContext(ctx, network, ips, port, opt)
}
-func serialDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
+func serialDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
return dualStackDialContext(ctx, serialDialContext, network, ips, port, opt)
}
-func concurrentSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
+func concurrentSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
return parallelDialContext(ctx, network, ips, port, opt)
}
-func concurrentDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
+func concurrentDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
if opt.prefer != 4 && opt.prefer != 6 {
return parallelDialContext(ctx, network, ips, port, opt)
}
return dualStackDialContext(ctx, parallelDialContext, network, ips, port, opt)
}
-func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
+func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
ipv4s, ipv6s := resolver.SortationAddr(ips)
if len(ipv4s) == 0 && len(ipv6s) == 0 {
return nil, ErrorNoIpAddress
@@ -285,7 +277,7 @@ loop:
return nil, errors.Join(errs...)
}
-func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
+func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
if len(ips) == 0 {
return nil, ErrorNoIpAddress
}
@@ -324,7 +316,7 @@ func parallelDialContext(ctx context.Context, network string, ips []netip.Addr,
return nil, os.ErrDeadlineExceeded
}
-func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
+func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
if len(ips) == 0 {
return nil, ErrorNoIpAddress
}
@@ -390,5 +382,5 @@ func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddr
func NewDialer(options ...Option) Dialer {
opt := applyOptions(options...)
- return Dialer{Opt: *opt}
+ return Dialer{Opt: opt}
}
diff --git a/clash-meta/component/dialer/options.go b/clash-meta/component/dialer/options.go
index d15d36e80e..bb978cdba6 100644
--- a/clash-meta/component/dialer/options.go
+++ b/clash-meta/component/dialer/options.go
@@ -10,7 +10,6 @@ import (
)
var (
- DefaultOptions []Option
DefaultInterface = atomic.NewTypedValue[string]("")
DefaultRoutingMark = atomic.NewInt32(0)
@@ -117,9 +116,13 @@ func WithOption(o option) Option {
}
func IsZeroOptions(opts []Option) bool {
- var opt option
- for _, o := range opts {
+ return applyOptions(opts...) == option{}
+}
+
+func applyOptions(options ...Option) option {
+ opt := option{}
+ for _, o := range options {
o(&opt)
}
- return opt == option{}
+ return opt
}
diff --git a/clash-meta/component/tls/reality.go b/clash-meta/component/tls/reality.go
index 5beffb4392..eee37384a8 100644
--- a/clash-meta/component/tls/reality.go
+++ b/clash-meta/component/tls/reality.go
@@ -37,9 +37,9 @@ type RealityConfig struct {
ShortID [RealityMaxShortIDLen]byte
}
-func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
+func GetRealityConn(ctx context.Context, conn net.Conn, clientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
retry := 0
- for fingerprint, exists := GetFingerprint(ClientFingerprint); exists; retry++ {
+ for fingerprint, exists := GetFingerprint(clientFingerprint); exists; retry++ {
verifier := &realityVerifier{
serverName: tlsConfig.ServerName,
}
diff --git a/clash-meta/listener/inbound/anytls_test.go b/clash-meta/listener/inbound/anytls_test.go
index 5f295f795e..9d172890d7 100644
--- a/clash-meta/listener/inbound/anytls_test.go
+++ b/clash-meta/listener/inbound/anytls_test.go
@@ -19,17 +19,23 @@ func testInboundAnyTLS(t *testing.T, inboundOptions inbound.AnyTLSOption, outbou
}
inboundOptions.Users = map[string]string{"test": userUUID}
in, err := inbound.NewAnyTLS(&inboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
tunnel := NewHttpTestTunnel()
defer tunnel.Close()
err = in.Listen(tunnel)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer in.Close()
addrPort, err := netip.ParseAddrPort(in.Address())
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
outboundOptions.Name = "anytls_outbound"
outboundOptions.Server = addrPort.Addr().String()
@@ -37,7 +43,9 @@ func testInboundAnyTLS(t *testing.T, inboundOptions inbound.AnyTLSOption, outbou
outboundOptions.Password = userUUID
out, err := outbound.NewAnyTLS(outboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/clash-meta/listener/inbound/common_test.go b/clash-meta/listener/inbound/common_test.go
index 80ec006bff..18a6eefa53 100644
--- a/clash-meta/listener/inbound/common_test.go
+++ b/clash-meta/listener/inbound/common_test.go
@@ -132,7 +132,9 @@ func NewHttpTestTunnel() *TestTunnel {
go http.Serve(ln, r)
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
req = req.WithContext(ctx)
var dstPort uint16 = 80
@@ -145,7 +147,9 @@ func NewHttpTestTunnel() *TestTunnel {
DstPort: dstPort,
}
instance, err := proxy.DialContext(ctx, metadata)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer instance.Close()
transport := &http.Transport{
@@ -174,14 +178,18 @@ func NewHttpTestTunnel() *TestTunnel {
defer client.CloseIdleConnections()
resp, err := client.Do(req)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
data, err := io.ReadAll(resp.Body)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
assert.Equal(t, httpData, data)
}
tunnel := &TestTunnel{
@@ -212,27 +220,31 @@ func NewHttpTestTunnel() *TestTunnel {
CloseFn: ln.Close,
DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) {
// Sequential testing for debugging
- testFn(t, proxy, "http")
- testFn(t, proxy, "https")
+ t.Run("Sequential", func(t *testing.T) {
+ testFn(t, proxy, "http")
+ testFn(t, proxy, "https")
+ })
// Concurrent testing to detect stress
- wg := sync.WaitGroup{}
- num := 50
- for i := 0; i < num; i++ {
- wg.Add(1)
- go func() {
- testFn(t, proxy, "https")
- defer wg.Done()
- }()
- }
- for i := 0; i < num; i++ {
- wg.Add(1)
- go func() {
- testFn(t, proxy, "http")
- defer wg.Done()
- }()
- }
- wg.Wait()
+ t.Run("Concurrent", func(t *testing.T) {
+ wg := sync.WaitGroup{}
+ const num = 50
+ for i := 0; i < num; i++ {
+ wg.Add(1)
+ go func() {
+ testFn(t, proxy, "https")
+ defer wg.Done()
+ }()
+ }
+ for i := 0; i < num; i++ {
+ wg.Add(1)
+ go func() {
+ testFn(t, proxy, "http")
+ defer wg.Done()
+ }()
+ }
+ wg.Wait()
+ })
},
}
return tunnel
diff --git a/clash-meta/listener/inbound/hysteria2_test.go b/clash-meta/listener/inbound/hysteria2_test.go
index 13e4115c58..2926a9e6ca 100644
--- a/clash-meta/listener/inbound/hysteria2_test.go
+++ b/clash-meta/listener/inbound/hysteria2_test.go
@@ -19,17 +19,23 @@ func testInboundHysteria2(t *testing.T, inboundOptions inbound.Hysteria2Option,
}
inboundOptions.Users = map[string]string{"test": userUUID}
in, err := inbound.NewHysteria2(&inboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
tunnel := NewHttpTestTunnel()
defer tunnel.Close()
err = in.Listen(tunnel)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer in.Close()
addrPort, err := netip.ParseAddrPort(in.Address())
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
outboundOptions.Name = "hysteria2_outbound"
outboundOptions.Server = addrPort.Addr().String()
@@ -37,7 +43,9 @@ func testInboundHysteria2(t *testing.T, inboundOptions inbound.Hysteria2Option,
outboundOptions.Password = userUUID
out, err := outbound.NewHysteria2(outboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/clash-meta/listener/inbound/mux_test.go b/clash-meta/listener/inbound/mux_test.go
index 68c4a5aba4..4e676e6d96 100644
--- a/clash-meta/listener/inbound/mux_test.go
+++ b/clash-meta/listener/inbound/mux_test.go
@@ -32,7 +32,9 @@ func testSingMux(t *testing.T, tunnel *TestTunnel, out outbound.ProxyAdapter) {
Protocol: protocol,
}
out, err := outbound.NewSingMux(singMuxOption, ¬CloseProxyAdapter{out})
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/clash-meta/listener/inbound/shadowsocks_test.go b/clash-meta/listener/inbound/shadowsocks_test.go
index 770afbdd18..cf72c55c88 100644
--- a/clash-meta/listener/inbound/shadowsocks_test.go
+++ b/clash-meta/listener/inbound/shadowsocks_test.go
@@ -57,17 +57,23 @@ func testInboundShadowSocks0(t *testing.T, inboundOptions inbound.ShadowSocksOpt
}
inboundOptions.Password = password
in, err := inbound.NewShadowSocks(&inboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
tunnel := NewHttpTestTunnel()
defer tunnel.Close()
err = in.Listen(tunnel)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer in.Close()
addrPort, err := netip.ParseAddrPort(in.Address())
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
outboundOptions.Name = "shadowsocks_outbound"
outboundOptions.Server = addrPort.Addr().String()
@@ -75,7 +81,9 @@ func testInboundShadowSocks0(t *testing.T, inboundOptions inbound.ShadowSocksOpt
outboundOptions.Password = password
out, err := outbound.NewShadowSocks(outboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/clash-meta/listener/inbound/trojan_test.go b/clash-meta/listener/inbound/trojan_test.go
index c6ecbea8bc..320081f8c6 100644
--- a/clash-meta/listener/inbound/trojan_test.go
+++ b/clash-meta/listener/inbound/trojan_test.go
@@ -21,17 +21,23 @@ func testInboundTrojan(t *testing.T, inboundOptions inbound.TrojanOption, outbou
{Username: "test", Password: userUUID},
}
in, err := inbound.NewTrojan(&inboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
tunnel := NewHttpTestTunnel()
defer tunnel.Close()
err = in.Listen(tunnel)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer in.Close()
addrPort, err := netip.ParseAddrPort(in.Address())
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
outboundOptions.Name = "trojan_outbound"
outboundOptions.Server = addrPort.Addr().String()
@@ -39,7 +45,9 @@ func testInboundTrojan(t *testing.T, inboundOptions inbound.TrojanOption, outbou
outboundOptions.Password = userUUID
out, err := outbound.NewTrojan(outboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/clash-meta/listener/inbound/tuic_test.go b/clash-meta/listener/inbound/tuic_test.go
index 4b9753c83c..24865d8339 100644
--- a/clash-meta/listener/inbound/tuic_test.go
+++ b/clash-meta/listener/inbound/tuic_test.go
@@ -48,24 +48,32 @@ func testInboundTuic0(t *testing.T, inboundOptions inbound.TuicOption, outboundO
Port: "0",
}
in, err := inbound.NewTuic(&inboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
tunnel := NewHttpTestTunnel()
defer tunnel.Close()
err = in.Listen(tunnel)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer in.Close()
addrPort, err := netip.ParseAddrPort(in.Address())
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
outboundOptions.Name = "tuic_outbound"
outboundOptions.Server = addrPort.Addr().String()
outboundOptions.Port = int(addrPort.Port())
out, err := outbound.NewTuic(outboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/clash-meta/listener/inbound/vless_test.go b/clash-meta/listener/inbound/vless_test.go
index 04f441f173..f19cad348f 100644
--- a/clash-meta/listener/inbound/vless_test.go
+++ b/clash-meta/listener/inbound/vless_test.go
@@ -21,17 +21,23 @@ func testInboundVless(t *testing.T, inboundOptions inbound.VlessOption, outbound
{Username: "test", UUID: userUUID, Flow: "xtls-rprx-vision"},
}
in, err := inbound.NewVless(&inboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
tunnel := NewHttpTestTunnel()
defer tunnel.Close()
err = in.Listen(tunnel)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer in.Close()
addrPort, err := netip.ParseAddrPort(in.Address())
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
outboundOptions.Name = "vless_outbound"
outboundOptions.Server = addrPort.Addr().String()
@@ -39,7 +45,9 @@ func testInboundVless(t *testing.T, inboundOptions inbound.VlessOption, outbound
outboundOptions.UUID = userUUID
out, err := outbound.NewVless(outboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/clash-meta/listener/inbound/vmess_test.go b/clash-meta/listener/inbound/vmess_test.go
index a9b99de7d3..57af5b0b90 100644
--- a/clash-meta/listener/inbound/vmess_test.go
+++ b/clash-meta/listener/inbound/vmess_test.go
@@ -21,17 +21,23 @@ func testInboundVMess(t *testing.T, inboundOptions inbound.VmessOption, outbound
{Username: "test", UUID: userUUID, AlterID: 0},
}
in, err := inbound.NewVmess(&inboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
tunnel := NewHttpTestTunnel()
defer tunnel.Close()
err = in.Listen(tunnel)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer in.Close()
addrPort, err := netip.ParseAddrPort(in.Address())
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
outboundOptions.Name = "vmess_outbound"
outboundOptions.Server = addrPort.Addr().String()
@@ -41,7 +47,9 @@ func testInboundVMess(t *testing.T, inboundOptions inbound.VmessOption, outbound
outboundOptions.Cipher = "auto"
out, err := outbound.NewVmess(outboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/clash-meta/transport/anytls/client.go b/clash-meta/transport/anytls/client.go
index ea99b43883..abd8364a0c 100644
--- a/clash-meta/transport/anytls/client.go
+++ b/clash-meta/transport/anytls/client.go
@@ -9,7 +9,6 @@ import (
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/buf"
- C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/anytls/padding"
"github.com/metacubex/mihomo/transport/anytls/session"
"github.com/metacubex/mihomo/transport/vmess"
@@ -83,12 +82,7 @@ func (c *Client) CreateOutboundTLSConnection(ctx context.Context) (net.Conn, err
b.WriteZeroN(paddingLen)
}
- getTlsConn := func() (net.Conn, error) {
- ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
- defer cancel()
- return vmess.StreamTLSConn(ctx, conn, c.tlsConfig)
- }
- tlsConn, err := getTlsConn()
+ tlsConn, err := vmess.StreamTLSConn(ctx, conn, c.tlsConfig)
if err != nil {
conn.Close()
return nil, err
diff --git a/clash-meta/transport/gun/gun.go b/clash-meta/transport/gun/gun.go
index b9f03ce762..8889baa753 100644
--- a/clash-meta/transport/gun/gun.go
+++ b/clash-meta/transport/gun/gun.go
@@ -224,7 +224,7 @@ func (g *Conn) SetDeadline(t time.Time) error {
return nil
}
-func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, realityConfig *tlsC.RealityConfig) *TransportWrap {
+func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, clientFingerprint string, realityConfig *tlsC.RealityConfig) *TransportWrap {
dialFunc := func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
defer cancel()
@@ -237,9 +237,13 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
return pconn, nil
}
- if len(Fingerprint) != 0 {
+ clientFingerprint := clientFingerprint
+ if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
+ clientFingerprint = tlsC.GetGlobalFingerprint()
+ }
+ if len(clientFingerprint) != 0 {
if realityConfig == nil {
- if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists {
+ if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
utlsConn := tlsC.UClient(pconn, cfg, fingerprint)
if err := utlsConn.HandshakeContext(ctx); err != nil {
pconn.Close()
@@ -253,7 +257,7 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
return utlsConn, nil
}
} else {
- realityConn, err := tlsC.GetRealityConn(ctx, pconn, Fingerprint, cfg, realityConfig)
+ realityConn, err := tlsC.GetRealityConn(ctx, pconn, clientFingerprint, cfg, realityConfig)
if err != nil {
pconn.Close()
return nil, err
diff --git a/clash-meta/transport/sing-shadowtls/shadowtls.go b/clash-meta/transport/sing-shadowtls/shadowtls.go
index 7bfd45787f..a628e7b168 100644
--- a/clash-meta/transport/sing-shadowtls/shadowtls.go
+++ b/clash-meta/transport/sing-shadowtls/shadowtls.go
@@ -45,13 +45,7 @@ func NewShadowTLS(ctx context.Context, conn net.Conn, option *ShadowTLSOption) (
return nil, err
}
- var clientHelloID utls.ClientHelloID
- if len(option.ClientFingerprint) != 0 {
- if fingerprint, exists := tlsC.GetFingerprint(option.ClientFingerprint); exists {
- clientHelloID = *fingerprint.ClientHelloID
- }
- }
- tlsHandshake := uTLSHandshakeFunc(tlsConfig, clientHelloID)
+ tlsHandshake := uTLSHandshakeFunc(tlsConfig, option.ClientFingerprint)
client, err := shadowtls.NewClient(shadowtls.ClientConfig{
Version: option.Version,
Password: option.Password,
@@ -64,7 +58,7 @@ func NewShadowTLS(ctx context.Context, conn net.Conn, option *ShadowTLSOption) (
return client.DialContextConn(ctx, conn)
}
-func uTLSHandshakeFunc(config *tls.Config, clientHelloID utls.ClientHelloID) shadowtls.TLSHandshakeFunc {
+func uTLSHandshakeFunc(config *tls.Config, clientFingerprint string) shadowtls.TLSHandshakeFunc {
return func(ctx context.Context, conn net.Conn, sessionIDGenerator shadowtls.TLSSessionIDGeneratorFunc) error {
tlsConfig := &utls.Config{
Rand: config.Rand,
@@ -84,12 +78,18 @@ func uTLSHandshakeFunc(config *tls.Config, clientHelloID utls.ClientHelloID) sha
Renegotiation: utls.RenegotiationSupport(config.Renegotiation),
SessionIDGenerator: sessionIDGenerator,
}
- var empty utls.ClientHelloID
- if clientHelloID == empty {
- tlsConn := utls.Client(conn, tlsConfig)
- return tlsConn.Handshake()
+ clientFingerprint := clientFingerprint
+ if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
+ clientFingerprint = tlsC.GetGlobalFingerprint()
}
- tlsConn := utls.UClient(conn, tlsConfig, clientHelloID)
+ if len(clientFingerprint) != 0 {
+ if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
+ clientHelloID := *fingerprint.ClientHelloID
+ tlsConn := utls.UClient(conn, tlsConfig, clientHelloID)
+ return tlsConn.HandshakeContext(ctx)
+ }
+ }
+ tlsConn := utls.Client(conn, tlsConfig)
return tlsConn.HandshakeContext(ctx)
}
}
diff --git a/clash-meta/transport/trojan/trojan.go b/clash-meta/transport/trojan/trojan.go
index e500050238..93819130fd 100644
--- a/clash-meta/transport/trojan/trojan.go
+++ b/clash-meta/transport/trojan/trojan.go
@@ -1,24 +1,17 @@
package trojan
import (
- "context"
"crypto/sha256"
- "crypto/tls"
"encoding/binary"
"encoding/hex"
"errors"
"io"
"net"
- "net/http"
"sync"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/pool"
- "github.com/metacubex/mihomo/component/ca"
- tlsC "github.com/metacubex/mihomo/component/tls"
- C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5"
- "github.com/metacubex/mihomo/transport/vmess"
)
const (
@@ -27,8 +20,8 @@ const (
)
var (
- defaultALPN = []string{"h2", "http/1.1"}
- defaultWebsocketALPN = []string{"http/1.1"}
+ DefaultALPN = []string{"h2", "http/1.1"}
+ DefaultWebsocketALPN = []string{"http/1.1"}
crlf = []byte{'\r', '\n'}
)
@@ -43,115 +36,11 @@ const (
KeyLength = 56
)
-type Option struct {
- Password string
- ALPN []string
- ServerName string
- SkipCertVerify bool
- Fingerprint string
- ClientFingerprint string
- Reality *tlsC.RealityConfig
-}
-
-type WebsocketOption struct {
- Host string
- Port string
- Path string
- Headers http.Header
- V2rayHttpUpgrade bool
- V2rayHttpUpgradeFastOpen bool
-}
-
-type Trojan struct {
- option *Option
- hexPassword [KeyLength]byte
-}
-
-func (t *Trojan) StreamConn(ctx context.Context, conn net.Conn) (net.Conn, error) {
- alpn := defaultALPN
- if len(t.option.ALPN) != 0 {
- alpn = t.option.ALPN
- }
- tlsConfig := &tls.Config{
- NextProtos: alpn,
- MinVersion: tls.VersionTLS12,
- InsecureSkipVerify: t.option.SkipCertVerify,
- ServerName: t.option.ServerName,
- }
-
- var err error
- tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint)
- if err != nil {
- return nil, err
- }
-
- if len(t.option.ClientFingerprint) != 0 {
- if t.option.Reality == nil {
- utlsConn, valid := vmess.GetUTLSConn(conn, t.option.ClientFingerprint, tlsConfig)
- if valid {
- ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
- defer cancel()
-
- err := utlsConn.HandshakeContext(ctx)
- return utlsConn, err
- }
- } else {
- ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
- defer cancel()
- return tlsC.GetRealityConn(ctx, conn, t.option.ClientFingerprint, tlsConfig, t.option.Reality)
- }
- }
- if t.option.Reality != nil {
- return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
- }
-
- tlsConn := tls.Client(conn, tlsConfig)
-
- // fix tls handshake not timeout
- ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
- defer cancel()
-
- err = tlsConn.HandshakeContext(ctx)
- return tlsConn, err
-}
-
-func (t *Trojan) StreamWebsocketConn(ctx context.Context, conn net.Conn, wsOptions *WebsocketOption) (net.Conn, error) {
- alpn := defaultWebsocketALPN
- if len(t.option.ALPN) != 0 {
- alpn = t.option.ALPN
- }
-
- tlsConfig := &tls.Config{
- NextProtos: alpn,
- MinVersion: tls.VersionTLS12,
- InsecureSkipVerify: t.option.SkipCertVerify,
- ServerName: t.option.ServerName,
- }
-
- var err error
- tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint)
- if err != nil {
- return nil, err
- }
-
- return vmess.StreamWebsocketConn(ctx, conn, &vmess.WebsocketConfig{
- Host: wsOptions.Host,
- Port: wsOptions.Port,
- Path: wsOptions.Path,
- Headers: wsOptions.Headers,
- V2rayHttpUpgrade: wsOptions.V2rayHttpUpgrade,
- V2rayHttpUpgradeFastOpen: wsOptions.V2rayHttpUpgradeFastOpen,
- TLS: true,
- TLSConfig: tlsConfig,
- ClientFingerprint: t.option.ClientFingerprint,
- })
-}
-
-func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error {
+func WriteHeader(w io.Writer, hexPassword [KeyLength]byte, command Command, socks5Addr []byte) error {
buf := pool.GetBuffer()
defer pool.PutBuffer(buf)
- buf.Write(t.hexPassword[:])
+ buf.Write(hexPassword[:])
buf.Write(crlf)
buf.WriteByte(command)
@@ -162,12 +51,6 @@ func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) er
return err
}
-func (t *Trojan) PacketConn(conn net.Conn) net.PacketConn {
- return &PacketConn{
- Conn: conn,
- }
-}
-
func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
buf := pool.GetBuffer()
defer pool.PutBuffer(buf)
@@ -243,10 +126,6 @@ func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, int, error) {
return uAddr, length, total - length, nil
}
-func New(option *Option) *Trojan {
- return &Trojan{option, Key(option.Password)}
-}
-
var _ N.EnhancePacketConn = (*PacketConn)(nil)
type PacketConn struct {
diff --git a/clash-meta/transport/vmess/tls.go b/clash-meta/transport/vmess/tls.go
index 82a484f1b8..ff622995a0 100644
--- a/clash-meta/transport/vmess/tls.go
+++ b/clash-meta/transport/vmess/tls.go
@@ -32,15 +32,22 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn
return nil, err
}
- if len(cfg.ClientFingerprint) != 0 {
+ clientFingerprint := cfg.ClientFingerprint
+ if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
+ clientFingerprint = tlsC.GetGlobalFingerprint()
+ }
+ if len(clientFingerprint) != 0 {
if cfg.Reality == nil {
- utlsConn, valid := GetUTLSConn(conn, cfg.ClientFingerprint, tlsConfig)
- if valid {
+ if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
+ utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
err = utlsConn.HandshakeContext(ctx)
- return utlsConn, err
+ if err != nil {
+ return nil, err
+ }
+ return utlsConn, nil
}
} else {
- return tlsC.GetRealityConn(ctx, conn, cfg.ClientFingerprint, tlsConfig, cfg.Reality)
+ return tlsC.GetRealityConn(ctx, conn, clientFingerprint, tlsConfig, cfg.Reality)
}
}
if cfg.Reality != nil {
@@ -52,14 +59,3 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn
err = tlsConn.HandshakeContext(ctx)
return tlsConn, err
}
-
-func GetUTLSConn(conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config) (*tlsC.UConn, bool) {
-
- if fingerprint, exists := tlsC.GetFingerprint(ClientFingerprint); exists {
- utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
-
- return utlsConn, true
- }
-
- return nil, false
-}
diff --git a/clash-meta/transport/vmess/websocket.go b/clash-meta/transport/vmess/websocket.go
index 8ada88ecc1..43b695ee93 100644
--- a/clash-meta/transport/vmess/websocket.go
+++ b/clash-meta/transport/vmess/websocket.go
@@ -354,8 +354,12 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig,
config.ServerName = uri.Host
}
- if len(c.ClientFingerprint) != 0 {
- if fingerprint, exists := tlsC.GetFingerprint(c.ClientFingerprint); exists {
+ clientFingerprint := c.ClientFingerprint
+ if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
+ clientFingerprint = tlsC.GetGlobalFingerprint()
+ }
+ if len(clientFingerprint) != 0 {
+ if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
utlsConn := tlsC.UClient(conn, config, fingerprint)
if err = utlsConn.BuildWebsocketHandshakeState(); err != nil {
return nil, fmt.Errorf("parse url %s error: %w", c.Path, err)
diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json
index b8c0406afd..eb0b75c95a 100644
--- a/clash-nyanpasu/frontend/nyanpasu/package.json
+++ b/clash-nyanpasu/frontend/nyanpasu/package.json
@@ -53,12 +53,12 @@
"@csstools/normalize.css": "12.1.1",
"@emotion/babel-plugin": "11.13.5",
"@emotion/react": "11.14.0",
- "@iconify/json": "2.2.328",
+ "@iconify/json": "2.2.329",
"@monaco-editor/react": "4.7.0",
"@tanstack/react-query": "5.71.10",
- "@tanstack/react-router": "1.114.34",
- "@tanstack/router-devtools": "1.114.34",
- "@tanstack/router-plugin": "1.114.34",
+ "@tanstack/react-router": "1.116.0",
+ "@tanstack/router-devtools": "1.116.0",
+ "@tanstack/router-plugin": "1.116.1",
"@tauri-apps/plugin-clipboard-manager": "2.2.2",
"@tauri-apps/plugin-dialog": "2.2.0",
"@tauri-apps/plugin-fs": "2.2.0",
diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json
index d6aad731b9..70327b7906 100644
--- a/clash-nyanpasu/manifest/version.json
+++ b/clash-nyanpasu/manifest/version.json
@@ -2,10 +2,10 @@
"manifest_version": 1,
"latest": {
"mihomo": "v1.19.4",
- "mihomo_alpha": "alpha-3d806b5",
+ "mihomo_alpha": "alpha-69ce4d0",
"clash_rs": "v0.7.7",
"clash_premium": "2023-09-05-gdcc8d87",
- "clash_rs_alpha": "0.7.7-alpha+sha.5300c66"
+ "clash_rs_alpha": "0.7.7-alpha+sha.76e1309"
},
"arch_template": {
"mihomo": {
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
}
},
- "updated_at": "2025-04-16T22:20:55.831Z"
+ "updated_at": "2025-04-17T22:20:47.969Z"
}
diff --git a/clash-nyanpasu/package.json b/clash-nyanpasu/package.json
index ba3612deaa..1c6a5d2e80 100644
--- a/clash-nyanpasu/package.json
+++ b/clash-nyanpasu/package.json
@@ -85,7 +85,7 @@
"eslint-plugin-react-compiler": "19.0.0-beta-ebf51a3-20250411",
"eslint-plugin-react-hooks": "5.2.0",
"globals": "16.0.0",
- "knip": "5.50.4",
+ "knip": "5.50.5",
"lint-staged": "15.5.1",
"neostandard": "0.12.1",
"npm-run-all2": "7.0.2",
diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml
index 4dc2e8ed12..1e24b34e82 100644
--- a/clash-nyanpasu/pnpm-lock.yaml
+++ b/clash-nyanpasu/pnpm-lock.yaml
@@ -103,8 +103,8 @@ importers:
specifier: 16.0.0
version: 16.0.0
knip:
- specifier: 5.50.4
- version: 5.50.4(@types/node@22.13.17)(typescript@5.8.2)
+ specifier: 5.50.5
+ version: 5.50.5(@types/node@22.13.17)(typescript@5.8.2)
lint-staged:
specifier: 15.5.1
version: 15.5.1
@@ -246,7 +246,7 @@ importers:
version: 4.0.17
'@tanstack/router-zod-adapter':
specifier: 1.81.5
- version: 1.81.5(@tanstack/react-router@1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(zod@3.24.3)
+ version: 1.81.5(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(zod@3.24.3)
'@tauri-apps/api':
specifier: 2.4.0
version: 2.4.0
@@ -333,8 +333,8 @@ importers:
specifier: 11.14.0
version: 11.14.0(@types/react@19.0.12)(react@19.1.0)
'@iconify/json':
- specifier: 2.2.328
- version: 2.2.328
+ specifier: 2.2.329
+ version: 2.2.329
'@monaco-editor/react':
specifier: 4.7.0
version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -342,14 +342,14 @@ importers:
specifier: 5.71.10
version: 5.71.10(react@19.1.0)
'@tanstack/react-router':
- specifier: 1.114.34
- version: 1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ specifier: 1.116.0
+ version: 1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tanstack/router-devtools':
- specifier: 1.114.34
- version: 1.114.34(@tanstack/react-router@1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.114.33)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)
+ specifier: 1.116.0
+ version: 1.116.0(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.115.3)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)
'@tanstack/router-plugin':
- specifier: 1.114.34
- version: 1.114.34(@tanstack/react-router@1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.2.6(@types/node@22.13.17)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.86.3)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
+ specifier: 1.116.1
+ version: 1.116.1(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.2.6(@types/node@22.13.17)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.86.3)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
'@tauri-apps/plugin-clipboard-manager':
specifier: 2.2.2
version: 2.2.2
@@ -1680,8 +1680,8 @@ packages:
'@vue/compiler-sfc':
optional: true
- '@iconify/json@2.2.328':
- resolution: {integrity: sha512-hdkS6oE+U3tfR2onc1QcHZh+2hkKHjLEkagtdQHDZnx/OS2sFIDc6/XoEh+2BtuzDXfyANgGV/Kpd6oy0OBvqQ==}
+ '@iconify/json@2.2.329':
+ resolution: {integrity: sha512-t8h9fj+u5Fjre4jifYaC1SPqzIxZ8OIJefzSlLpjX0+2ejrcXKiYl+slIQ1uv2Eco28c+gzkvrwO7cXr3sELMw==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -2811,8 +2811,8 @@ packages:
'@tailwindcss/postcss@4.0.17':
resolution: {integrity: sha512-qeJbRTB5FMZXmuJF+eePd235EGY6IyJZF0Bh0YM6uMcCI4L9Z7dy+lPuLAhxOJzxnajsbjPoDAKOuAqZRtf1PQ==}
- '@tanstack/history@1.114.29':
- resolution: {integrity: sha512-OTRMhwidScQSA0xsc5OCtm3K/oAChe47jy1e4OY3VpXUnKrI7C8iwfQ9XDRdpdsRASH2xi6P5I0+7ksFBehaQQ==}
+ '@tanstack/history@1.115.0':
+ resolution: {integrity: sha512-K7JJNrRVvyjAVnbXOH2XLRhFXDkeP54Kt2P4FR1Kl2KDGlIbkua5VqZQD2rot3qaDrpufyUa63nuLai1kOLTsQ==}
engines: {node: '>=12'}
'@tanstack/match-sorter-utils@8.19.4':
@@ -2827,16 +2827,16 @@ packages:
peerDependencies:
react: ^18 || ^19
- '@tanstack/react-router-devtools@1.114.34':
- resolution: {integrity: sha512-TBSYUitRGu4s0s+NA14tzfXG+KlIApv4oPEGTKv9qBhfmDqAYsxcCzVsncXpNrxC+qBKHkzSJikmgBZNu0e5sQ==}
+ '@tanstack/react-router-devtools@1.116.0':
+ resolution: {integrity: sha512-PsJZWPjcmwZGe71kUvH4bI1ozkv1FgBuBEE0hTYlTCSJ3uG+qv3ndGEI+AiFyuF5OStrbfg0otW1OxeNq5vdGQ==}
engines: {node: '>=12'}
peerDependencies:
- '@tanstack/react-router': ^1.114.34
+ '@tanstack/react-router': ^1.116.0
react: '>=18.0.0 || >=19.0.0'
react-dom: '>=18.0.0 || >=19.0.0'
- '@tanstack/react-router@1.114.34':
- resolution: {integrity: sha512-J2HOgnhc5AY31Y5cVMkWJRDw6rdZiRx2heLCNtxDnIXVKvK/hc3rKLPUEqqTS9VoPW8P+aSK3f7ggPtkrtn06A==}
+ '@tanstack/react-router@1.116.0':
+ resolution: {integrity: sha512-ZBAg5Q6zJf0mnP9DYPiaaQ/wLDH2ujCMi/2RllpH86VUkdkyvQQzpAyKoiYJ891wh9OPgj6W6tPrzB4qy5FpRA==}
engines: {node: '>=12'}
peerDependencies:
react: '>=18.0.0 || >=19.0.0'
@@ -2861,15 +2861,15 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- '@tanstack/router-core@1.114.33':
- resolution: {integrity: sha512-jSBo7R+oat3k///Q4XpgNp9sVveQdjdmju5a7u2ibi8V/qPXEAaaYh57vMXvIOpE3ZDDLPYLjWPAf+SvHV8JeA==}
+ '@tanstack/router-core@1.115.3':
+ resolution: {integrity: sha512-gynHs72LHVg05fuJTwZZYhDL4VNEAK0sXz7IqiBv7a3qsYeEmIZsGaFr9sVjTkuF1kbrFBdJd5JYutzBh9Uuhw==}
engines: {node: '>=12'}
- '@tanstack/router-devtools-core@1.114.33':
- resolution: {integrity: sha512-BXSPVwhet2edTYF+Td+38AvUQTyFnv9WYF5QMUP3ODO8nx9BLvV7ABPfgUL77xvfdXFSYDLgOno1Ep+jkNmeqw==}
+ '@tanstack/router-devtools-core@1.115.3':
+ resolution: {integrity: sha512-VBdgw1qxeOD/6FlZ9gitrWPUKGW83CuAW31gf32E0dxL7sIXP+yEFyPlNsVlENan1oSaEuV8tjKkuq5s4MfaPw==}
engines: {node: '>=12'}
peerDependencies:
- '@tanstack/router-core': ^1.114.33
+ '@tanstack/router-core': ^1.115.3
csstype: ^3.0.10
solid-js: '>=1.9.5'
tiny-invariant: ^1.3.3
@@ -2877,11 +2877,11 @@ packages:
csstype:
optional: true
- '@tanstack/router-devtools@1.114.34':
- resolution: {integrity: sha512-lu3qC8ZpunGg5nFCvbz8/evbDqvkL7X5133kwgfog7fb/lIhs+4NMe/+MWtqm5kJnvnylYV+2ETwDBKZ/oZ0bw==}
+ '@tanstack/router-devtools@1.116.0':
+ resolution: {integrity: sha512-Tx8UD+JbRA8Fgo0fDLcfdnH43+CVhbFi6KSUvdhHFBSdDfQnrrNQsDfNqml2fAI89VBEDsfxrwU2lwAF2R/1qw==}
engines: {node: '>=12'}
peerDependencies:
- '@tanstack/react-router': ^1.114.34
+ '@tanstack/react-router': ^1.116.0
csstype: ^3.0.10
react: '>=18.0.0 || >=19.0.0'
react-dom: '>=18.0.0 || >=19.0.0'
@@ -2889,21 +2889,21 @@ packages:
csstype:
optional: true
- '@tanstack/router-generator@1.114.34':
- resolution: {integrity: sha512-+lBA2LAoffzBaGHWKT/YeEgwN/aUZMIhbtsbifLsqGIyKmXXbg+U/CQz8uO5Nqv4m36SmhjevOoVUxkPZbEPDg==}
+ '@tanstack/router-generator@1.116.0':
+ resolution: {integrity: sha512-XhCp85zP87G2bpSXnosiP3fiMo8HMQD2mvWqFFTFKz87WocabQYGlfhmNYWmBwI50EuS7Ph9lwXsSkV0oKh0xw==}
engines: {node: '>=12'}
peerDependencies:
- '@tanstack/react-router': ^1.114.34
+ '@tanstack/react-router': ^1.116.0
peerDependenciesMeta:
'@tanstack/react-router':
optional: true
- '@tanstack/router-plugin@1.114.34':
- resolution: {integrity: sha512-G3OxypoRnijDKIlCJkJ29+Zq2b050nqDCbhZYz2yMIvfzYB2BnKLpJSHmQuT9AEiM5drrUgL5WdGlUcRU3tNxg==}
+ '@tanstack/router-plugin@1.116.1':
+ resolution: {integrity: sha512-9A8DAyRejTzvkVOzgVPUY6l2aH7xOMEXSJJtV9GNbi4NtE6AXUCoFe3mtvYnHSzRqAUMCO0wnfVENCjXQoQYZw==}
engines: {node: '>=12'}
peerDependencies:
'@rsbuild/core': '>=1.0.2'
- '@tanstack/react-router': ^1.114.34
+ '@tanstack/react-router': ^1.116.0
vite: '>=5.0.0 || >=6.0.0'
vite-plugin-solid: ^2.11.2
webpack: '>=5.92.0'
@@ -2919,8 +2919,8 @@ packages:
webpack:
optional: true
- '@tanstack/router-utils@1.114.29':
- resolution: {integrity: sha512-RDn3aMOHPrXYCQGXNaN4P0MvwiuCZHBKTO9srtLqYYCzW2iipqbyZ53RI54TzPgNLE37jtN5XaEH4FNF0Ydodg==}
+ '@tanstack/router-utils@1.115.0':
+ resolution: {integrity: sha512-Dng4y+uLR9b5zPGg7dHReHOTHQa6x+G6nCoZshsDtWrYsrdCcJEtLyhwZ5wG8OyYS6dVr/Cn+E5Bd2b6BhJ89w==}
engines: {node: '>=12'}
'@tanstack/router-zod-adapter@1.81.5':
@@ -2940,8 +2940,8 @@ packages:
'@tanstack/virtual-core@3.11.2':
resolution: {integrity: sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==}
- '@tanstack/virtual-file-routes@1.114.29':
- resolution: {integrity: sha512-DufKsQy/qxDpOTiggJCgshhJkpSyUygwHHfl2LA66CXOf3aUjZtlNu4io1UpmJNf8C/9lVlGARhkoq5fTRRk0w==}
+ '@tanstack/virtual-file-routes@1.115.0':
+ resolution: {integrity: sha512-XLUh1Py3AftcERrxkxC5Y5m5mfllRH3YR6YVlyjFgI2Tc2Ssy2NKmQFQIafoxfW459UJ8Dn81nWKETEIJifE4g==}
engines: {node: '>=12'}
'@taplo/core@0.2.0':
@@ -5706,8 +5706,8 @@ packages:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
- knip@5.50.4:
- resolution: {integrity: sha512-In+GjPpd2P3IDZnBBP4QF27vhQOhuBkICiuN9j+DMOf/m/qAFLGcbvuAGxco8IDvf26pvBnfeSmm1f6iNCkgOA==}
+ knip@5.50.5:
+ resolution: {integrity: sha512-I3mfebuG5x8i/mJJA41xjnmHMbLw75ymbDxlS7HMP+4CjY+jXEDSJyP3A2xmI5JF5/o47Fr8D7Pq3BVT0/nQPw==}
engines: {node: '>=18.18.0'}
hasBin: true
peerDependencies:
@@ -9545,7 +9545,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@iconify/json@2.2.328':
+ '@iconify/json@2.2.329':
dependencies:
'@iconify/types': 2.0.0
pathe: 1.1.2
@@ -10671,7 +10671,7 @@ snapshots:
postcss: 8.5.3
tailwindcss: 4.0.17
- '@tanstack/history@1.114.29': {}
+ '@tanstack/history@1.115.0': {}
'@tanstack/match-sorter-utils@8.19.4':
dependencies:
@@ -10684,10 +10684,10 @@ snapshots:
'@tanstack/query-core': 5.71.10
react: 19.1.0
- '@tanstack/react-router-devtools@1.114.34(@tanstack/react-router@1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.114.33)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)':
+ '@tanstack/react-router-devtools@1.116.0(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.115.3)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)':
dependencies:
- '@tanstack/react-router': 1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@tanstack/router-devtools-core': 1.114.33(@tanstack/router-core@1.114.33)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)
+ '@tanstack/react-router': 1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@tanstack/router-devtools-core': 1.115.3(@tanstack/router-core@1.115.3)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
solid-js: 1.9.5
@@ -10696,11 +10696,11 @@ snapshots:
- csstype
- tiny-invariant
- '@tanstack/react-router@1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ '@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
- '@tanstack/history': 1.114.29
+ '@tanstack/history': 1.115.0
'@tanstack/react-store': 0.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@tanstack/router-core': 1.114.33
+ '@tanstack/router-core': 1.115.3
jsesc: 3.1.0
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
@@ -10726,15 +10726,15 @@ snapshots:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
- '@tanstack/router-core@1.114.33':
+ '@tanstack/router-core@1.115.3':
dependencies:
- '@tanstack/history': 1.114.29
+ '@tanstack/history': 1.115.0
'@tanstack/store': 0.7.0
tiny-invariant: 1.3.3
- '@tanstack/router-devtools-core@1.114.33(@tanstack/router-core@1.114.33)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)':
+ '@tanstack/router-devtools-core@1.115.3(@tanstack/router-core@1.115.3)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)':
dependencies:
- '@tanstack/router-core': 1.114.33
+ '@tanstack/router-core': 1.115.3
clsx: 2.1.1
goober: 2.1.16(csstype@3.1.3)
solid-js: 1.9.5
@@ -10742,10 +10742,10 @@ snapshots:
optionalDependencies:
csstype: 3.1.3
- '@tanstack/router-devtools@1.114.34(@tanstack/react-router@1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.114.33)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)':
+ '@tanstack/router-devtools@1.116.0(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.115.3)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)':
dependencies:
- '@tanstack/react-router': 1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@tanstack/react-router-devtools': 1.114.34(@tanstack/react-router@1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.114.33)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)
+ '@tanstack/react-router': 1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@tanstack/react-router-devtools': 1.116.0(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.115.3)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)
clsx: 2.1.1
goober: 2.1.16(csstype@3.1.3)
react: 19.1.0
@@ -10756,16 +10756,16 @@ snapshots:
- '@tanstack/router-core'
- tiny-invariant
- '@tanstack/router-generator@1.114.34(@tanstack/react-router@1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0))':
+ '@tanstack/router-generator@1.116.0(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))':
dependencies:
- '@tanstack/virtual-file-routes': 1.114.29
+ '@tanstack/virtual-file-routes': 1.115.0
prettier: 3.5.3
tsx: 4.19.3
zod: 3.24.3
optionalDependencies:
- '@tanstack/react-router': 1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@tanstack/react-router': 1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@tanstack/router-plugin@1.114.34(@tanstack/react-router@1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.2.6(@types/node@22.13.17)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.86.3)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))':
+ '@tanstack/router-plugin@1.116.1(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.2.6(@types/node@22.13.17)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.86.3)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))':
dependencies:
'@babel/core': 7.26.9
'@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.9)
@@ -10773,10 +10773,10 @@ snapshots:
'@babel/template': 7.26.9
'@babel/traverse': 7.26.9
'@babel/types': 7.26.9
- '@tanstack/router-core': 1.114.33
- '@tanstack/router-generator': 1.114.34(@tanstack/react-router@1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0))
- '@tanstack/router-utils': 1.114.29
- '@tanstack/virtual-file-routes': 1.114.29
+ '@tanstack/router-core': 1.115.3
+ '@tanstack/router-generator': 1.116.0(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))
+ '@tanstack/router-utils': 1.115.0
+ '@tanstack/virtual-file-routes': 1.115.0
'@types/babel__core': 7.20.5
'@types/babel__template': 7.4.4
'@types/babel__traverse': 7.20.6
@@ -10785,21 +10785,21 @@ snapshots:
unplugin: 2.2.2
zod: 3.24.3
optionalDependencies:
- '@tanstack/react-router': 1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@tanstack/react-router': 1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
vite: 6.2.6(@types/node@22.13.17)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.86.3)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)
transitivePeerDependencies:
- supports-color
- '@tanstack/router-utils@1.114.29':
+ '@tanstack/router-utils@1.115.0':
dependencies:
'@babel/generator': 7.26.9
'@babel/parser': 7.26.9
ansis: 3.12.0
diff: 7.0.0
- '@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(zod@3.24.3)':
+ '@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(zod@3.24.3)':
dependencies:
- '@tanstack/react-router': 1.114.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@tanstack/react-router': 1.116.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
zod: 3.24.3
'@tanstack/store@0.7.0': {}
@@ -10808,7 +10808,7 @@ snapshots:
'@tanstack/virtual-core@3.11.2': {}
- '@tanstack/virtual-file-routes@1.114.29': {}
+ '@tanstack/virtual-file-routes@1.115.0': {}
'@taplo/core@0.2.0': {}
@@ -13966,7 +13966,7 @@ snapshots:
kind-of@6.0.3: {}
- knip@5.50.4(@types/node@22.13.17)(typescript@5.8.2):
+ knip@5.50.5(@types/node@22.13.17)(typescript@5.8.2):
dependencies:
'@nodelib/fs.walk': 1.2.8
'@types/node': 22.13.17
diff --git a/clash-verge-rev/src/components/setting/setting-verge-basic.tsx b/clash-verge-rev/src/components/setting/setting-verge-basic.tsx
index 7666e9c8d9..0fadef0e70 100644
--- a/clash-verge-rev/src/components/setting/setting-verge-basic.tsx
+++ b/clash-verge-rev/src/components/setting/setting-verge-basic.tsx
@@ -182,6 +182,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
diff --git a/clash-verge-rev/src/locales/ar.json b/clash-verge-rev/src/locales/ar.json
index 3ebe5a500d..ea131dbeb3 100644
--- a/clash-verge-rev/src/locales/ar.json
+++ b/clash-verge-rev/src/locales/ar.json
@@ -185,6 +185,7 @@
"Rule Provider": "مزود القواعد",
"Logs": "السجلات",
"Pause": "إيقاف مؤقت",
+ "Resume": "استأنف",
"Clear": "مسح",
"Test": "اختبار",
"Test All": "اختبار الكل",
diff --git a/clash-verge-rev/src/locales/en.json b/clash-verge-rev/src/locales/en.json
index 09d0a22136..ea699e99f6 100644
--- a/clash-verge-rev/src/locales/en.json
+++ b/clash-verge-rev/src/locales/en.json
@@ -189,6 +189,7 @@
"Rule Provider": "Rule Provider",
"Logs": "Logs",
"Pause": "Pause",
+ "Resume": "Resume",
"Clear": "Clear",
"Test": "Test",
"Test All": "Test All",
diff --git a/clash-verge-rev/src/locales/fa.json b/clash-verge-rev/src/locales/fa.json
index cdcd3307c9..1b12d56dd9 100644
--- a/clash-verge-rev/src/locales/fa.json
+++ b/clash-verge-rev/src/locales/fa.json
@@ -185,6 +185,7 @@
"Rule Provider": "تأمینکننده قانون",
"Logs": "لاگها",
"Pause": "توقف",
+ "Resume": "از سرگیری",
"Clear": "پاک کردن",
"Test": "آزمون",
"Test All": "آزمون همه",
diff --git a/clash-verge-rev/src/locales/id.json b/clash-verge-rev/src/locales/id.json
index 1ebd59aca2..4aeb4fc805 100644
--- a/clash-verge-rev/src/locales/id.json
+++ b/clash-verge-rev/src/locales/id.json
@@ -217,6 +217,7 @@
"Rule Provider": "Penyedia Aturan",
"Logs": "Log",
"Pause": "Jeda",
+ "Resume": "Lanjut",
"Clear": "Bersihkan",
"Test": "Tes",
"Test All": "Tes Semua",
diff --git a/clash-verge-rev/src/locales/ru.json b/clash-verge-rev/src/locales/ru.json
index 8d6deddedd..a0737ecf35 100644
--- a/clash-verge-rev/src/locales/ru.json
+++ b/clash-verge-rev/src/locales/ru.json
@@ -188,6 +188,7 @@
"Rule Provider": "Провайдеры правил",
"Logs": "Логи",
"Pause": "Пауза",
+ "Resume": "Возобновить",
"Clear": "Очистить",
"Test": "Тест",
"Test All": "Тестировать все",
diff --git a/clash-verge-rev/src/locales/tt.json b/clash-verge-rev/src/locales/tt.json
index 71abb38494..2dbfa84a2d 100644
--- a/clash-verge-rev/src/locales/tt.json
+++ b/clash-verge-rev/src/locales/tt.json
@@ -185,6 +185,7 @@
"Rule Provider": "Кагыйдә провайдеры",
"Logs": "Логлар",
"Pause": "Туктау",
+ "Resume": "Дәвам",
"Clear": "Чистарту",
"Test": "Тест",
"Test All": "Барчасын тестлау",
diff --git a/clash-verge-rev/src/locales/zh.json b/clash-verge-rev/src/locales/zh.json
index 9bb72dfa66..f2ba304b63 100644
--- a/clash-verge-rev/src/locales/zh.json
+++ b/clash-verge-rev/src/locales/zh.json
@@ -189,6 +189,7 @@
"Rule Provider": "规则集合",
"Logs": "日志",
"Pause": "暂停",
+ "Resume": "继续",
"Clear": "清除",
"Test": "测试",
"Test All": "测试全部",
diff --git a/clash-verge-rev/src/pages/_layout.tsx b/clash-verge-rev/src/pages/_layout.tsx
index 8282a4d3f8..ae8acacd9b 100644
--- a/clash-verge-rev/src/pages/_layout.tsx
+++ b/clash-verge-rev/src/pages/_layout.tsx
@@ -287,6 +287,7 @@ const Layout = () => {
{
header={
+Signed-off-by: Michael Riesch
+---
+ drivers/clk/rockchip/clk-rk3568.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/drivers/clk/rockchip/clk-rk3568.c b/drivers/clk/rockchip/clk-rk3568.c
+index 53d10b1c627b..7d9279291e76 100644
+--- a/drivers/clk/rockchip/clk-rk3568.c
++++ b/drivers/clk/rockchip/clk-rk3568.c
+@@ -1602,6 +1602,7 @@ static const char *const rk3568_cru_critical_clocks[] __initconst = {
+ "pclk_php",
+ "hclk_usb",
+ "pclk_usb",
++ "hclk_vi",
+ "hclk_vo",
+ };
+
+
+---
diff --git a/lede/target/linux/rockchip/patches-6.12/311-01-v6.13-initial-support-for-rk3576-ufs-controller.patch b/lede/target/linux/rockchip/patches-6.12/311-01-v6.13-initial-support-for-rk3576-ufs-controller.patch
new file mode 100644
index 0000000000..3bae9a8340
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/311-01-v6.13-initial-support-for-rk3576-ufs-controller.patch
@@ -0,0 +1,138 @@
+Document Rockchip UFS host controller for RK3576 SoC.
+
+Reviewed-by: Krzysztof Kozlowski
+Signed-off-by: Shawn Lin
+---
+
+Changes in v5:
+- use ufshc for devicetree example suggested by Mani
+
+Changes in v4:
+- properly describe reset-gpios
+
+Changes in v3:
+- rename the file to rockchip,rk3576-ufshc.yaml
+- add description for reset-gpios
+- use rockchip,rk3576-ufshc as compatible
+
+Changes in v2:
+- rename the file
+- add reset-gpios
+
+ .../bindings/ufs/rockchip,rk3576-ufshc.yaml | 105 +++++++++++++++++++++
+ 1 file changed, 105 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/ufs/rockchip,rk3576-ufshc.yaml
+
+diff --git a/Documentation/devicetree/bindings/ufs/rockchip,rk3576-ufshc.yaml b/Documentation/devicetree/bindings/ufs/rockchip,rk3576-ufshc.yaml
+new file mode 100644
+index 0000000..7d6c038
+--- /dev/null
++++ b/Documentation/devicetree/bindings/ufs/rockchip,rk3576-ufshc.yaml
+@@ -0,0 +1,105 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/ufs/rockchip,rk3576-ufshc.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Rockchip UFS Host Controller
++
++maintainers:
++ - Shawn Lin
++
++allOf:
++ - $ref: ufs-common.yaml
++
++properties:
++ compatible:
++ const: rockchip,rk3576-ufshc
++
++ reg:
++ maxItems: 5
++
++ reg-names:
++ items:
++ - const: hci
++ - const: mphy
++ - const: hci_grf
++ - const: mphy_grf
++ - const: hci_apb
++
++ clocks:
++ maxItems: 4
++
++ clock-names:
++ items:
++ - const: core
++ - const: pclk
++ - const: pclk_mphy
++ - const: ref_out
++
++ power-domains:
++ maxItems: 1
++
++ resets:
++ maxItems: 4
++
++ reset-names:
++ items:
++ - const: biu
++ - const: sys
++ - const: ufs
++ - const: grf
++
++ reset-gpios:
++ maxItems: 1
++ description: |
++ GPIO specifiers for host to reset the whole UFS device including PHY and
++ memory. This gpio is active low and should choose the one whose high output
++ voltage is lower than 1.5V based on the UFS spec.
++
++required:
++ - compatible
++ - reg
++ - reg-names
++ - clocks
++ - clock-names
++ - interrupts
++ - power-domains
++ - resets
++ - reset-names
++ - reset-gpios
++
++unevaluatedProperties: false
++
++examples:
++ - |
++ #include
++ #include
++ #include
++ #include
++ #include
++ #include
++
++ soc {
++ #address-cells = <2>;
++ #size-cells = <2>;
++
++ ufshc: ufshc@2a2d0000 {
++ compatible = "rockchip,rk3576-ufshc";
++ reg = <0x0 0x2a2d0000 0x0 0x10000>,
++ <0x0 0x2b040000 0x0 0x10000>,
++ <0x0 0x2601f000 0x0 0x1000>,
++ <0x0 0x2603c000 0x0 0x1000>,
++ <0x0 0x2a2e0000 0x0 0x10000>;
++ reg-names = "hci", "mphy", "hci_grf", "mphy_grf", "hci_apb";
++ clocks = <&cru ACLK_UFS_SYS>, <&cru PCLK_USB_ROOT>, <&cru PCLK_MPHY>,
++ <&cru CLK_REF_UFS_CLKOUT>;
++ clock-names = "core", "pclk", "pclk_mphy", "ref_out";
++ interrupts = ;
++ power-domains = <&power RK3576_PD_USB>;
++ resets = <&cru SRST_A_UFS_BIU>, <&cru SRST_A_UFS_SYS>, <&cru SRST_A_UFS>,
++ <&cru SRST_P_UFS_GRF>;
++ reset-names = "biu", "sys", "ufs", "grf";
++ reset-gpios = <&gpio4 RK_PD0 GPIO_ACTIVE_LOW>;
++ };
++ };
+--
+2.7.4
diff --git a/lede/target/linux/rockchip/patches-6.12/311-02-v6.13-initial-support-for-rk3576-ufs-controller.patch b/lede/target/linux/rockchip/patches-6.12/311-02-v6.13-initial-support-for-rk3576-ufs-controller.patch
new file mode 100644
index 0000000000..2df0c32206
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/311-02-v6.13-initial-support-for-rk3576-ufs-controller.patch
@@ -0,0 +1,31 @@
+Add ROCKCHIP_SIP_SUSPEND_MODE to pass down parameters to Trusted Firmware
+in order to decide suspend mode. Currently only add ROCKCHIP_SLEEP_PD_CONFIG
+which teaches firmware to power down controllers or not.
+
+Signed-off-by: Shawn Lin
+---
+
+Changes in v5: None
+Changes in v4: None
+Changes in v3: None
+Changes in v2: None
+
+ include/soc/rockchip/rockchip_sip.h | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/include/soc/rockchip/rockchip_sip.h b/include/soc/rockchip/rockchip_sip.h
+index c46a9ae..501ad1f 100644
+--- a/include/soc/rockchip/rockchip_sip.h
++++ b/include/soc/rockchip/rockchip_sip.h
+@@ -6,6 +6,9 @@
+ #ifndef __SOC_ROCKCHIP_SIP_H
+ #define __SOC_ROCKCHIP_SIP_H
+
++#define ROCKCHIP_SIP_SUSPEND_MODE 0x82000003
++#define ROCKCHIP_SLEEP_PD_CONFIG 0xff
++
+ #define ROCKCHIP_SIP_DRAM_FREQ 0x82000008
+ #define ROCKCHIP_SIP_CONFIG_DRAM_INIT 0x00
+ #define ROCKCHIP_SIP_CONFIG_DRAM_SET_RATE 0x01
+--
+2.7.4
diff --git a/lede/target/linux/rockchip/patches-6.12/311-03-v6.13-initial-support-for-rk3576-ufs-controller.patch b/lede/target/linux/rockchip/patches-6.12/311-03-v6.13-initial-support-for-rk3576-ufs-controller.patch
new file mode 100644
index 0000000000..317ac91b03
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/311-03-v6.13-initial-support-for-rk3576-ufs-controller.patch
@@ -0,0 +1,110 @@
+From: Ulf Hansson
+
+For some usecases a consumer driver requires its device to remain power-on
+from the PM domain perspective during runtime. Using dev PM qos along with
+the genpd governors, doesn't work for this case as would potentially
+prevent the device from being runtime suspended too.
+
+To support these usecases, let's introduce dev_pm_genpd_rpm_always_on() to
+allow consumers drivers to dynamically control the behaviour in genpd for a
+device that is attached to it.
+
+Signed-off-by: Ulf Hansson
+Signed-off-by: Shawn Lin
+---
+
+Changes in v5: None
+Changes in v4: None
+Changes in v3: None
+Changes in v2: None
+
+ drivers/pmdomain/core.c | 34 ++++++++++++++++++++++++++++++++++
+ include/linux/pm_domain.h | 7 +++++++
+ 2 files changed, 41 insertions(+)
+
+diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c
+index 5ede0f7..2ccfcb7 100644
+--- a/drivers/pmdomain/core.c
++++ b/drivers/pmdomain/core.c
+@@ -692,6 +692,36 @@ bool dev_pm_genpd_get_hwmode(struct device *dev)
+ }
+ EXPORT_SYMBOL_GPL(dev_pm_genpd_get_hwmode);
+
++/**
++ * dev_pm_genpd_rpm_always_on() - Control if the PM domain can be powered off.
++ *
++ * @dev: Device for which the PM domain may need to stay on for.
++ * @on: Value to set or unset for the condition.
++ *
++ * For some usecases a consumer driver requires its device to remain power-on
++ * from the PM domain perspective during runtime. This function allows the
++ * behaviour to be dynamically controlled for a device attached to a genpd.
++ *
++ * It is assumed that the users guarantee that the genpd wouldn't be detached
++ * while this routine is getting called.
++ *
++ * Return: Returns 0 on success and negative error values on failures.
++ */
++int dev_pm_genpd_rpm_always_on(struct device *dev, bool on)
++{
++ struct generic_pm_domain *genpd;
++
++ genpd = dev_to_genpd_safe(dev);
++ if (!genpd)
++ return -ENODEV;
++
++ genpd_lock(genpd);
++ dev_gpd_data(dev)->rpm_always_on = on;
++ genpd_unlock(genpd);
++
++ return 0;
++}
++
+ static int _genpd_power_on(struct generic_pm_domain *genpd, bool timed)
+ {
+ unsigned int state_idx = genpd->state_idx;
+@@ -863,6 +893,10 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on,
+ if (!pm_runtime_suspended(pdd->dev) ||
+ irq_safe_dev_in_sleep_domain(pdd->dev, genpd))
+ not_suspended++;
++
++ /* The device may need its PM domain to stay powered on. */
++ if (to_gpd_data(pdd)->rpm_always_on)
++ return -EBUSY;
+ }
+
+ if (not_suspended > 1 || (not_suspended == 1 && !one_dev_on))
+diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h
+index b637ec1..30186ad 100644
+--- a/include/linux/pm_domain.h
++++ b/include/linux/pm_domain.h
+@@ -245,6 +245,7 @@ struct generic_pm_domain_data {
+ unsigned int default_pstate;
+ unsigned int rpm_pstate;
+ bool hw_mode;
++ bool rpm_always_on;
+ void *data;
+ };
+
+@@ -277,6 +278,7 @@ ktime_t dev_pm_genpd_get_next_hrtimer(struct device *dev);
+ void dev_pm_genpd_synced_poweroff(struct device *dev);
+ int dev_pm_genpd_set_hwmode(struct device *dev, bool enable);
+ bool dev_pm_genpd_get_hwmode(struct device *dev);
++int dev_pm_genpd_rpm_always_on(struct device *dev, bool on);
+
+ extern struct dev_power_governor simple_qos_governor;
+ extern struct dev_power_governor pm_domain_always_on_gov;
+@@ -360,6 +362,11 @@ static inline bool dev_pm_genpd_get_hwmode(struct device *dev)
+ return false;
+ }
+
++static inline int dev_pm_genpd_rpm_always_on(struct device *dev, bool on)
++{
++ return -EOPNOTSUPP;
++}
++
+ #define simple_qos_governor (*(struct dev_power_governor *)(NULL))
+ #define pm_domain_always_on_gov (*(struct dev_power_governor *)(NULL))
+ #endif
+--
+2.7.4
diff --git a/lede/target/linux/rockchip/patches-6.12/311-04-v6.13-initial-support-for-rk3576-ufs-controller.patch b/lede/target/linux/rockchip/patches-6.12/311-04-v6.13-initial-support-for-rk3576-ufs-controller.patch
new file mode 100644
index 0000000000..f616787519
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/311-04-v6.13-initial-support-for-rk3576-ufs-controller.patch
@@ -0,0 +1,58 @@
+Inform firmware to keep the power domain on or off.
+
+Suggested-by: Ulf Hansson
+Signed-off-by: Shawn Lin
+---
+
+Changes in v5:
+- fix a compile warning
+
+Changes in v4: None
+Changes in v3: None
+Changes in v2: None
+
+ drivers/pmdomain/rockchip/pm-domains.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c
+index cb0f938..49842f1 100644
+--- a/drivers/pmdomain/rockchip/pm-domains.c
++++ b/drivers/pmdomain/rockchip/pm-domains.c
+@@ -5,6 +5,7 @@
+ * Copyright (c) 2015 ROCKCHIP, Co. Ltd.
+ */
+
++#include
+ #include
+ #include
+ #include
+@@ -20,6 +21,7 @@
+ #include
+ #include
+ #include
++#include
+ #include
+ #include
+ #include
+@@ -540,6 +542,7 @@ static void rockchip_do_pmu_set_power_domain(struct rockchip_pm_domain *pd,
+ struct generic_pm_domain *genpd = &pd->genpd;
+ u32 pd_pwr_offset = pd->info->pwr_offset;
+ bool is_on, is_mem_on = false;
++ struct arm_smccc_res res;
+
+ if (pd->info->pwr_mask == 0)
+ return;
+@@ -567,6 +570,11 @@ static void rockchip_do_pmu_set_power_domain(struct rockchip_pm_domain *pd,
+ genpd->name, is_on);
+ return;
+ }
++
++ /* Inform firmware to keep this pd on or off */
++ arm_smccc_smc(ROCKCHIP_SIP_SUSPEND_MODE, ROCKCHIP_SLEEP_PD_CONFIG,
++ pmu->info->pwr_offset + pd_pwr_offset,
++ pd->info->pwr_mask, on, 0, 0, 0, &res);
+ }
+
+ static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on)
+--
+2.7.4
diff --git a/lede/target/linux/rockchip/patches-6.12/311-05-v6.13-initial-support-for-rk3576-ufs-controller.patch b/lede/target/linux/rockchip/patches-6.12/311-05-v6.13-initial-support-for-rk3576-ufs-controller.patch
new file mode 100644
index 0000000000..6f675eaea8
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/311-05-v6.13-initial-support-for-rk3576-ufs-controller.patch
@@ -0,0 +1,68 @@
+These two APIs will be used by host driver if they need a different
+HCE process.
+
+Signed-off-by: Shawn Lin
+---
+
+Changes in v5: None
+Changes in v4: None
+Changes in v3: None
+Changes in v2: None
+
+ drivers/ufs/core/ufshcd.c | 6 ++++--
+ include/ufs/ufshcd.h | 2 ++
+ 2 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
+index 24a32e2..9d1d56d 100644
+--- a/drivers/ufs/core/ufshcd.c
++++ b/drivers/ufs/core/ufshcd.c
+@@ -4039,7 +4039,7 @@ static int ufshcd_dme_link_startup(struct ufs_hba *hba)
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+-static int ufshcd_dme_reset(struct ufs_hba *hba)
++int ufshcd_dme_reset(struct ufs_hba *hba)
+ {
+ struct uic_command uic_cmd = {
+ .command = UIC_CMD_DME_RESET,
+@@ -4053,6 +4053,7 @@ static int ufshcd_dme_reset(struct ufs_hba *hba)
+
+ return ret;
+ }
++EXPORT_SYMBOL_GPL(ufshcd_dme_reset);
+
+ int ufshcd_dme_configure_adapt(struct ufs_hba *hba,
+ int agreed_gear,
+@@ -4078,7 +4079,7 @@ EXPORT_SYMBOL_GPL(ufshcd_dme_configure_adapt);
+ *
+ * Return: 0 on success, non-zero value on failure.
+ */
+-static int ufshcd_dme_enable(struct ufs_hba *hba)
++int ufshcd_dme_enable(struct ufs_hba *hba)
+ {
+ struct uic_command uic_cmd = {
+ .command = UIC_CMD_DME_ENABLE,
+@@ -4092,6 +4093,7 @@ static int ufshcd_dme_enable(struct ufs_hba *hba)
+
+ return ret;
+ }
++EXPORT_SYMBOL_GPL(ufshcd_dme_enable);
+
+ static inline void ufshcd_add_delay_before_dme_cmd(struct ufs_hba *hba)
+ {
+diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
+index 3f68ae3e4..b9733dc 100644
+--- a/include/ufs/ufshcd.h
++++ b/include/ufs/ufshcd.h
+@@ -1360,6 +1360,8 @@ extern int ufshcd_system_thaw(struct device *dev);
+ extern int ufshcd_system_restore(struct device *dev);
+ #endif
+
++extern int ufshcd_dme_reset(struct ufs_hba *hba);
++extern int ufshcd_dme_enable(struct ufs_hba *hba);
+ extern int ufshcd_dme_configure_adapt(struct ufs_hba *hba,
+ int agreed_gear,
+ int adapt_val);
+--
+2.7.4
diff --git a/lede/target/linux/rockchip/patches-6.12/311-06-v6.13-initial-support-for-rk3576-ufs-controller.patch b/lede/target/linux/rockchip/patches-6.12/311-06-v6.13-initial-support-for-rk3576-ufs-controller.patch
new file mode 100644
index 0000000000..45af73ee32
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/311-06-v6.13-initial-support-for-rk3576-ufs-controller.patch
@@ -0,0 +1,515 @@
+RK3576 SoC contains a UFS controller, add initial support for it.
+The features are:
+(1) support UFS 2.0 features
+(2) High speed up to HS-G3
+(3) 2RX-2TX lanes
+(4) auto H8 entry and exit
+
+Software limitation:
+(1) HCE procedure: enable controller->enable intr->dme_reset->dme_enable
+(2) disable unipro timeout values before power mode change
+
+Signed-off-by: Shawn Lin
+---
+
+Changes in v5:
+- use device_set_awake_path() and disable ref_out_clk in suspend
+- remove pd_id from header
+- recontruct ufs_rockchip_hce_enable_notify() to workaround hce enable
+ without using new quirk
+
+Changes in v4:
+- deal with power domain of rpm and spm suggested by Ulf
+- Fix typo and disable clks in ufs_rockchip_remove
+- remove clk_disable_unprepare(host->ref_out_clk) from
+ ufs_rockchip_remove
+
+Changes in v3:
+- reword Kconfig description
+- elaborate more about controller in commit msg
+- use rockchip,rk3576-ufshc for compatible
+- remove useless header file
+- remove inline for ufshcd_is_device_present
+- use usleep_range instead
+- remove initialization, reverse Xmas order
+- remove useless varibles
+- check vops for null
+- other small fixes for err path
+- remove pm_runtime_set_active
+- fix the active and inactive reset-gpios logic
+- fix rpm_lvl and spm_lvl to 5 and move to end of probe path
+- remove unnecessary system PM callbacks
+- use UFSHCI_QUIRK_DME_RESET_ENABLE_AFTER_HCE instead
+ of UFSHCI_QUIRK_BROKEN_HCE
+
+Changes in v2: None
+
+ drivers/ufs/host/Kconfig | 12 ++
+ drivers/ufs/host/Makefile | 1 +
+ drivers/ufs/host/ufs-rockchip.c | 368 ++++++++++++++++++++++++++++++++++++++++
+ drivers/ufs/host/ufs-rockchip.h | 48 ++++++
+ 4 files changed, 429 insertions(+)
+ create mode 100644 drivers/ufs/host/ufs-rockchip.c
+ create mode 100644 drivers/ufs/host/ufs-rockchip.h
+
+diff --git a/drivers/ufs/host/Kconfig b/drivers/ufs/host/Kconfig
+index 580c8d0..191fbd7 100644
+--- a/drivers/ufs/host/Kconfig
++++ b/drivers/ufs/host/Kconfig
+@@ -142,3 +142,15 @@ config SCSI_UFS_SPRD
+
+ Select this if you have UFS controller on Unisoc chipset.
+ If unsure, say N.
++
++config SCSI_UFS_ROCKCHIP
++ tristate "Rockchip UFS host controller driver"
++ depends on SCSI_UFSHCD_PLATFORM && (ARCH_ROCKCHIP || COMPILE_TEST)
++ help
++ This selects the Rockchip specific additions to UFSHCD platform driver.
++ UFS host on Rockchip needs some vendor specific configuration before
++ accessing the hardware which includes PHY configuration and vendor
++ specific registers.
++
++ Select this if you have UFS controller on Rockchip chipset.
++ If unsure, say N.
+diff --git a/drivers/ufs/host/Makefile b/drivers/ufs/host/Makefile
+index 4573aea..2f97feb 100644
+--- a/drivers/ufs/host/Makefile
++++ b/drivers/ufs/host/Makefile
+@@ -10,5 +10,6 @@ obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
+ obj-$(CONFIG_SCSI_UFS_HISI) += ufs-hisi.o
+ obj-$(CONFIG_SCSI_UFS_MEDIATEK) += ufs-mediatek.o
+ obj-$(CONFIG_SCSI_UFS_RENESAS) += ufs-renesas.o
++obj-$(CONFIG_SCSI_UFS_ROCKCHIP) += ufs-rockchip.o
+ obj-$(CONFIG_SCSI_UFS_SPRD) += ufs-sprd.o
+ obj-$(CONFIG_SCSI_UFS_TI_J721E) += ti-j721e-ufs.o
+diff --git a/drivers/ufs/host/ufs-rockchip.c b/drivers/ufs/host/ufs-rockchip.c
+new file mode 100644
+index 0000000..b087ce0
+--- /dev/null
++++ b/drivers/ufs/host/ufs-rockchip.c
+@@ -0,0 +1,368 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Rockchip UFS Host Controller driver
++ *
++ * Copyright (C) 2024 Rockchip Electronics Co.Ltd.
++ */
++
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++
++#include
++#include
++#include "ufshcd-pltfrm.h"
++#include "ufs-rockchip.h"
++
++static int ufs_rockchip_hce_enable_notify(struct ufs_hba *hba,
++ enum ufs_notify_change_status status)
++{
++ int err = 0;
++
++ if (status == POST_CHANGE) {
++ err = ufshcd_dme_reset(hba);
++ if (err)
++ return err;
++
++ err = ufshcd_dme_enable(hba);
++ if (err)
++ return err;
++
++ err = ufshcd_vops_phy_initialization(hba);
++ }
++
++ return err;
++}
++
++static void ufs_rockchip_set_pm_lvl(struct ufs_hba *hba)
++{
++ hba->rpm_lvl = UFS_PM_LVL_5;
++ hba->spm_lvl = UFS_PM_LVL_5;
++}
++
++static int ufs_rockchip_rk3576_phy_init(struct ufs_hba *hba)
++{
++ struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
++
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(PA_LOCAL_TX_LCC_ENABLE, 0x0), 0x0);
++ /* enable the mphy DME_SET cfg */
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x200, 0x0), 0x40);
++ for (int i = 0; i < 2; i++) {
++ /* Configuration M-TX */
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xaa, SEL_TX_LANE0 + i), 0x06);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xa9, SEL_TX_LANE0 + i), 0x02);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xad, SEL_TX_LANE0 + i), 0x44);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xac, SEL_TX_LANE0 + i), 0xe6);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0xab, SEL_TX_LANE0 + i), 0x07);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x94, SEL_TX_LANE0 + i), 0x93);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x93, SEL_TX_LANE0 + i), 0xc9);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x7f, SEL_TX_LANE0 + i), 0x00);
++ /* Configuration M-RX */
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x12, SEL_RX_LANE0 + i), 0x06);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x11, SEL_RX_LANE0 + i), 0x00);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x1d, SEL_RX_LANE0 + i), 0x58);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x1c, SEL_RX_LANE0 + i), 0x8c);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x1b, SEL_RX_LANE0 + i), 0x02);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x25, SEL_RX_LANE0 + i), 0xf6);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x2f, SEL_RX_LANE0 + i), 0x69);
++ }
++ /* disable the mphy DME_SET cfg */
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x200, 0x0), 0x00);
++
++ ufs_sys_writel(host->mphy_base, 0x80, 0x08C);
++ ufs_sys_writel(host->mphy_base, 0xB5, 0x110);
++ ufs_sys_writel(host->mphy_base, 0xB5, 0x250);
++
++ ufs_sys_writel(host->mphy_base, 0x03, 0x134);
++ ufs_sys_writel(host->mphy_base, 0x03, 0x274);
++
++ ufs_sys_writel(host->mphy_base, 0x38, 0x0E0);
++ ufs_sys_writel(host->mphy_base, 0x38, 0x220);
++
++ ufs_sys_writel(host->mphy_base, 0x50, 0x164);
++ ufs_sys_writel(host->mphy_base, 0x50, 0x2A4);
++
++ ufs_sys_writel(host->mphy_base, 0x80, 0x178);
++ ufs_sys_writel(host->mphy_base, 0x80, 0x2B8);
++
++ ufs_sys_writel(host->mphy_base, 0x18, 0x1B0);
++ ufs_sys_writel(host->mphy_base, 0x18, 0x2F0);
++
++ ufs_sys_writel(host->mphy_base, 0x03, 0x128);
++ ufs_sys_writel(host->mphy_base, 0x03, 0x268);
++
++ ufs_sys_writel(host->mphy_base, 0x20, 0x12C);
++ ufs_sys_writel(host->mphy_base, 0x20, 0x26C);
++
++ ufs_sys_writel(host->mphy_base, 0xC0, 0x120);
++ ufs_sys_writel(host->mphy_base, 0xC0, 0x260);
++
++ ufs_sys_writel(host->mphy_base, 0x03, 0x094);
++
++ ufs_sys_writel(host->mphy_base, 0x03, 0x1B4);
++ ufs_sys_writel(host->mphy_base, 0x03, 0x2F4);
++
++ ufs_sys_writel(host->mphy_base, 0xC0, 0x08C);
++ usleep_range(1, 2);
++ ufs_sys_writel(host->mphy_base, 0x00, 0x08C);
++
++ usleep_range(200, 250);
++ /* start link up */
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(MIB_T_DBG_CPORT_TX_ENDIAN, 0), 0x0);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(MIB_T_DBG_CPORT_RX_ENDIAN, 0), 0x0);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(N_DEVICEID, 0), 0x0);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(N_DEVICEID_VALID, 0), 0x1);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(T_PEERDEVICEID, 0), 0x1);
++ ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(T_CONNECTIONSTATE, 0), 0x1);
++
++ return 0;
++}
++
++static int ufs_rockchip_common_init(struct ufs_hba *hba)
++{
++ struct device *dev = hba->dev;
++ struct platform_device *pdev = to_platform_device(dev);
++ struct ufs_rockchip_host *host;
++ int err;
++
++ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
++ if (!host)
++ return -ENOMEM;
++
++ /* system control register for hci */
++ host->ufs_sys_ctrl = devm_platform_ioremap_resource_byname(pdev, "hci_grf");
++ if (IS_ERR(host->ufs_sys_ctrl))
++ return dev_err_probe(dev, PTR_ERR(host->ufs_sys_ctrl),
++ "cannot ioremap for hci system control register\n");
++
++ /* system control register for mphy */
++ host->ufs_phy_ctrl = devm_platform_ioremap_resource_byname(pdev, "mphy_grf");
++ if (IS_ERR(host->ufs_phy_ctrl))
++ return dev_err_probe(dev, PTR_ERR(host->ufs_phy_ctrl),
++ "cannot ioremap for mphy system control register\n");
++
++ /* mphy base register */
++ host->mphy_base = devm_platform_ioremap_resource_byname(pdev, "mphy");
++ if (IS_ERR(host->mphy_base))
++ return dev_err_probe(dev, PTR_ERR(host->mphy_base),
++ "cannot ioremap for mphy base register\n");
++
++ host->rst = devm_reset_control_array_get_exclusive(dev);
++ if (IS_ERR(host->rst))
++ return dev_err_probe(dev, PTR_ERR(host->rst),
++ "failed to get reset control\n");
++
++ reset_control_assert(host->rst);
++ usleep_range(1, 2);
++ reset_control_deassert(host->rst);
++
++ host->ref_out_clk = devm_clk_get_enabled(dev, "ref_out");
++ if (IS_ERR(host->ref_out_clk))
++ return dev_err_probe(dev, PTR_ERR(host->ref_out_clk),
++ "ref_out unavailable\n");
++
++ host->rst_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW);
++ if (IS_ERR(host->rst_gpio))
++ return dev_err_probe(&pdev->dev, PTR_ERR(host->rst_gpio),
++ "invalid reset-gpios property in node\n");
++
++ host->clks[0].id = "core";
++ host->clks[1].id = "pclk";
++ host->clks[2].id = "pclk_mphy";
++ err = devm_clk_bulk_get_optional(dev, UFS_MAX_CLKS, host->clks);
++ if (err)
++ return dev_err_probe(dev, err, "failed to get clocks\n");
++
++ err = clk_bulk_prepare_enable(UFS_MAX_CLKS, host->clks);
++ if (err)
++ return dev_err_probe(dev, err, "failed to enable clocks\n");
++
++ host->hba = hba;
++
++ ufshcd_set_variant(hba, host);
++
++ return 0;
++}
++
++static int ufs_rockchip_rk3576_init(struct ufs_hba *hba)
++{
++ struct device *dev = hba->dev;
++ int ret;
++
++ hba->quirks = UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING;
++
++ /* Enable BKOPS when suspend */
++ hba->caps |= UFSHCD_CAP_AUTO_BKOPS_SUSPEND;
++ /* Enable putting device into deep sleep */
++ hba->caps |= UFSHCD_CAP_DEEPSLEEP;
++ /* Enable devfreq of UFS */
++ hba->caps |= UFSHCD_CAP_CLK_SCALING;
++ /* Enable WriteBooster */
++ hba->caps |= UFSHCD_CAP_WB_EN;
++
++ ret = ufs_rockchip_common_init(hba);
++ if (ret)
++ return dev_err_probe(dev, ret, "ufs common init fail\n");
++
++ return 0;
++}
++
++static int ufs_rockchip_device_reset(struct ufs_hba *hba)
++{
++ struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
++
++ /* Active the reset-gpios */
++ gpiod_set_value_cansleep(host->rst_gpio, 1);
++ usleep_range(20, 25);
++
++ /* Inactive the reset-gpios */
++ gpiod_set_value_cansleep(host->rst_gpio, 0);
++ usleep_range(20, 25);
++
++ return 0;
++}
++
++static const struct ufs_hba_variant_ops ufs_hba_rk3576_vops = {
++ .name = "rk3576",
++ .init = ufs_rockchip_rk3576_init,
++ .device_reset = ufs_rockchip_device_reset,
++ .hce_enable_notify = ufs_rockchip_hce_enable_notify,
++ .phy_initialization = ufs_rockchip_rk3576_phy_init,
++};
++
++static const struct of_device_id ufs_rockchip_of_match[] = {
++ { .compatible = "rockchip,rk3576-ufshc", .data = &ufs_hba_rk3576_vops },
++};
++MODULE_DEVICE_TABLE(of, ufs_rockchip_of_match);
++
++static int ufs_rockchip_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ const struct ufs_hba_variant_ops *vops;
++ struct ufs_hba *hba;
++ int err;
++
++ vops = device_get_match_data(dev);
++ if (!vops)
++ return dev_err_probe(dev, -EINVAL, "ufs_hba_variant_ops not defined.\n");
++
++ err = ufshcd_pltfrm_init(pdev, vops);
++ if (err)
++ return dev_err_probe(dev, err, "ufshcd_pltfrm_init failed\n");
++
++ hba = platform_get_drvdata(pdev);
++ /* Set the default desired pm level in case no users set via sysfs */
++ ufs_rockchip_set_pm_lvl(hba);
++
++ return 0;
++}
++
++static void ufs_rockchip_remove(struct platform_device *pdev)
++{
++ struct ufs_hba *hba = platform_get_drvdata(pdev);
++ struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
++
++ pm_runtime_forbid(&pdev->dev);
++ pm_runtime_get_noresume(&pdev->dev);
++ ufshcd_remove(hba);
++ ufshcd_dealloc_host(hba);
++ clk_bulk_disable_unprepare(UFS_MAX_CLKS, host->clks);
++}
++
++#ifdef CONFIG_PM
++static int ufs_rockchip_runtime_suspend(struct device *dev)
++{
++ struct ufs_hba *hba = dev_get_drvdata(dev);
++ struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
++
++ clk_disable_unprepare(host->ref_out_clk);
++
++ /* Shouldn't power down if rpm_lvl is less than level 5. */
++ dev_pm_genpd_rpm_always_on(dev, hba->rpm_lvl < UFS_PM_LVL_5 ? true : false);
++
++ return ufshcd_runtime_suspend(dev);
++}
++
++static int ufs_rockchip_runtime_resume(struct device *dev)
++{
++ struct ufs_hba *hba = dev_get_drvdata(dev);
++ struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
++ int err;
++
++ err = clk_prepare_enable(host->ref_out_clk);
++ if (err) {
++ dev_err(hba->dev, "failed to enable ref out clock %d\n", err);
++ return err;
++ }
++
++ reset_control_assert(host->rst);
++ usleep_range(1, 2);
++ reset_control_deassert(host->rst);
++
++ return ufshcd_runtime_resume(dev);
++}
++#endif
++
++#ifdef CONFIG_PM_SLEEP
++static int ufs_rockchip_system_suspend(struct device *dev)
++{
++ struct ufs_hba *hba = dev_get_drvdata(dev);
++ struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
++ int err;
++
++ if (hba->spm_lvl < UFS_PM_LVL_5)
++ device_set_awake_path(dev);
++
++ err = ufshcd_system_suspend(dev);
++ if (err) {
++ dev_err(hba->dev, "system susped failed %d\n", err);
++ return err;
++ }
++
++ clk_disable_unprepare(host->ref_out_clk);
++
++ return 0;
++}
++
++static int ufs_rockchip_system_resume(struct device *dev)
++{
++ struct ufs_hba *hba = dev_get_drvdata(dev);
++ struct ufs_rockchip_host *host = ufshcd_get_variant(hba);
++ int err;
++
++ err = clk_prepare_enable(host->ref_out_clk);
++ if (err) {
++ dev_err(hba->dev, "failed to enable ref out clock %d\n", err);
++ return err;
++ }
++
++ return ufshcd_system_resume(dev);
++}
++#endif
++
++static const struct dev_pm_ops ufs_rockchip_pm_ops = {
++ SET_SYSTEM_SLEEP_PM_OPS(ufs_rockchip_system_suspend, ufs_rockchip_system_resume)
++ SET_RUNTIME_PM_OPS(ufs_rockchip_runtime_suspend, ufs_rockchip_runtime_resume, NULL)
++ .prepare = ufshcd_suspend_prepare,
++ .complete = ufshcd_resume_complete,
++};
++
++static struct platform_driver ufs_rockchip_pltform = {
++ .probe = ufs_rockchip_probe,
++ .remove = ufs_rockchip_remove,
++ .driver = {
++ .name = "ufshcd-rockchip",
++ .pm = &ufs_rockchip_pm_ops,
++ .of_match_table = ufs_rockchip_of_match,
++ },
++};
++module_platform_driver(ufs_rockchip_pltform);
++
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("Rockchip UFS Host Driver");
+diff --git a/drivers/ufs/host/ufs-rockchip.h b/drivers/ufs/host/ufs-rockchip.h
+new file mode 100644
+index 0000000..768dbe3
+--- /dev/null
++++ b/drivers/ufs/host/ufs-rockchip.h
+@@ -0,0 +1,48 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/*
++ * Rockchip UFS Host Controller driver
++ *
++ * Copyright (C) 2024 Rockchip Electronics Co.Ltd.
++ */
++
++#ifndef _UFS_ROCKCHIP_H_
++#define _UFS_ROCKCHIP_H_
++
++#define UFS_MAX_CLKS 3
++
++#define SEL_TX_LANE0 0x0
++#define SEL_TX_LANE1 0x1
++#define SEL_TX_LANE2 0x2
++#define SEL_TX_LANE3 0x3
++#define SEL_RX_LANE0 0x4
++#define SEL_RX_LANE1 0x5
++#define SEL_RX_LANE2 0x6
++#define SEL_RX_LANE3 0x7
++
++#define MIB_T_DBG_CPORT_TX_ENDIAN 0xc022
++#define MIB_T_DBG_CPORT_RX_ENDIAN 0xc023
++
++struct ufs_rockchip_host {
++ struct ufs_hba *hba;
++ void __iomem *ufs_phy_ctrl;
++ void __iomem *ufs_sys_ctrl;
++ void __iomem *mphy_base;
++ struct gpio_desc *rst_gpio;
++ struct reset_control *rst;
++ struct clk *ref_out_clk;
++ struct clk_bulk_data clks[UFS_MAX_CLKS];
++ uint64_t caps;
++};
++
++#define ufs_sys_writel(base, val, reg) \
++ writel((val), (base) + (reg))
++#define ufs_sys_readl(base, reg) readl((base) + (reg))
++#define ufs_sys_set_bits(base, mask, reg) \
++ ufs_sys_writel( \
++ (base), ((mask) | (ufs_sys_readl((base), (reg)))), (reg))
++#define ufs_sys_ctrl_clr_bits(base, mask, reg) \
++ ufs_sys_writel((base), \
++ ((~(mask)) & (ufs_sys_readl((base), (reg)))), \
++ (reg))
++
++#endif /* _UFS_ROCKCHIP_H_ */
+--
+2.7.4
diff --git a/lede/target/linux/rockchip/patches-6.12/311-07-v6.13-initial-support-for-rk3576-ufs-controller.patch b/lede/target/linux/rockchip/patches-6.12/311-07-v6.13-initial-support-for-rk3576-ufs-controller.patch
new file mode 100644
index 0000000000..cc0bcd4e34
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/311-07-v6.13-initial-support-for-rk3576-ufs-controller.patch
@@ -0,0 +1,51 @@
+Add ufshc node to rk3576.dtsi, so the board using UFS could
+enable it.
+
+Signed-off-by: Shawn Lin
+---
+
+Changes in v5: None
+Changes in v4: None
+Changes in v3: None
+Changes in v2: None
+
+ arch/arm64/boot/dts/rockchip/rk3576.dtsi | 25 +++++++++++++++++++++++++
+ 1 file changed, 25 insertions(+)
+
+diff --git a/arch/arm64/boot/dts/rockchip/rk3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576.dtsi
+index 436232f..32beda2 100644
+--- a/arch/arm64/boot/dts/rockchip/rk3576.dtsi
++++ b/arch/arm64/boot/dts/rockchip/rk3576.dtsi
+@@ -1110,6 +1110,30 @@
+ };
+ };
+
++ ufshc: ufshc@2a2d0000 {
++ compatible = "rockchip,rk3576-ufshc";
++ reg = <0x0 0x2a2d0000 0 0x10000>, /* 0: HCI standard */
++ <0x0 0x2b040000 0 0x10000>, /* 1: Mphy */
++ <0x0 0x2601f000 0 0x1000>, /* 2: HCI Vendor specified */
++ <0x0 0x2603c000 0 0x1000>, /* 3: Mphy Vendor specified */
++ <0x0 0x2a2e0000 0 0x10000>; /* 4: HCI apb */
++ reg-names = "hci", "mphy", "hci_grf", "mphy_grf", "hci_apb";
++ clocks = <&cru ACLK_UFS_SYS>, <&cru PCLK_USB_ROOT>, <&cru PCLK_MPHY>,
++ <&cru CLK_REF_UFS_CLKOUT>;
++ clock-names = "core", "pclk", "pclk_mphy", "ref_out";
++ assigned-clocks = <&cru CLK_REF_OSC_MPHY>;
++ assigned-clock-parents = <&cru CLK_REF_MPHY_26M>;
++ interrupts = ;
++ power-domains = <&power RK3576_PD_USB>;
++ pinctrl-0 = <&ufs_refclk>;
++ pinctrl-names = "default";
++ resets = <&cru SRST_A_UFS_BIU>, <&cru SRST_A_UFS_SYS>,
++ <&cru SRST_A_UFS>, <&cru SRST_P_UFS_GRF>;
++ reset-names = "biu", "sys", "ufs", "grf";
++ reset-gpios = <&gpio4 RK_PD0 GPIO_ACTIVE_LOW>;
++ status = "disabled";
++ };
++
+ sdmmc: mmc@2a310000 {
+ compatible = "rockchip,rk3576-dw-mshc";
+ reg = <0x0 0x2a310000 0x0 0x4000>;
+--
+2.7.4
diff --git a/lede/target/linux/rockchip/patches-6.12/312-01-v6.13-rk3576-otp-support.patch b/lede/target/linux/rockchip/patches-6.12/312-01-v6.13-rk3576-otp-support.patch
new file mode 100644
index 0000000000..b6c56cd61b
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/312-01-v6.13-rk3576-otp-support.patch
@@ -0,0 +1,23 @@
+The phy clock of the OTP block is also present, but was not defined
+so far. Though its clk-id already existed, so just define its location.
+
+Signed-off-by: Heiko Stuebner
+---
+ drivers/clk/rockchip/clk-rk3576.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/drivers/clk/rockchip/clk-rk3576.c b/drivers/clk/rockchip/clk-rk3576.c
+index 595e010341f7..029939a98416 100644
+--- a/drivers/clk/rockchip/clk-rk3576.c
++++ b/drivers/clk/rockchip/clk-rk3576.c
+@@ -541,6 +541,8 @@ static struct rockchip_clk_branch rk3576_clk_branches[] __initdata = {
+ RK3576_CLKGATE_CON(5), 14, GFLAGS),
+ GATE(CLK_OTPC_AUTO_RD_G, "clk_otpc_auto_rd_g", "xin24m", 0,
+ RK3576_CLKGATE_CON(5), 15, GFLAGS),
++ GATE(CLK_OTP_PHY_G, "clk_otp_phy_g", "xin24m", 0,
++ RK3588_CLKGATE_CON(6), 0, GFLAGS),
+ COMPOSITE(CLK_MIPI_CAMERAOUT_M0, "clk_mipi_cameraout_m0", mux_24m_spll_gpll_cpll_p, 0,
+ RK3576_CLKSEL_CON(38), 8, 2, MFLAGS, 0, 8, DFLAGS,
+ RK3576_CLKGATE_CON(6), 3, GFLAGS),
+--
+2.45.2
diff --git a/lede/target/linux/rockchip/patches-6.12/312-02-v6.13-rk3576-otp-support.patch b/lede/target/linux/rockchip/patches-6.12/312-02-v6.13-rk3576-otp-support.patch
new file mode 100644
index 0000000000..8a9d65b86f
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/312-02-v6.13-rk3576-otp-support.patch
@@ -0,0 +1,52 @@
+The RK3588 has an offset into the OTP area where the readable area begins
+and automatically adds this to the start address.
+Other variants are very much similar to rk3588, just with a different
+offset, so move that value into variant-data.
+
+To match the size in bytes, store this value also in bytes and not in
+number of blocks.
+
+Signed-off-by: Heiko Stuebner
+---
+ drivers/nvmem/rockchip-otp.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/nvmem/rockchip-otp.c b/drivers/nvmem/rockchip-otp.c
+index ebc3f0b24166..3edfbfc2d722 100644
+--- a/drivers/nvmem/rockchip-otp.c
++++ b/drivers/nvmem/rockchip-otp.c
+@@ -59,7 +59,6 @@
+ #define RK3588_OTPC_AUTO_EN 0x08
+ #define RK3588_OTPC_INT_ST 0x84
+ #define RK3588_OTPC_DOUT0 0x20
+-#define RK3588_NO_SECURE_OFFSET 0x300
+ #define RK3588_NBYTES 4
+ #define RK3588_BURST_NUM 1
+ #define RK3588_BURST_SHIFT 8
+@@ -69,6 +68,7 @@
+
+ struct rockchip_data {
+ int size;
++ int read_offset;
+ const char * const *clks;
+ int num_clks;
+ nvmem_reg_read_t reg_read;
+@@ -196,7 +196,7 @@ static int rk3588_otp_read(void *context, unsigned int offset,
+ addr_start = round_down(offset, RK3588_NBYTES) / RK3588_NBYTES;
+ addr_end = round_up(offset + bytes, RK3588_NBYTES) / RK3588_NBYTES;
+ addr_len = addr_end - addr_start;
+- addr_start += RK3588_NO_SECURE_OFFSET;
++ addr_start += otp->data->read_offset / RK3588_NBYTES;
+
+ buf = kzalloc(array_size(addr_len, RK3588_NBYTES), GFP_KERNEL);
+ if (!buf)
+@@ -280,6 +280,7 @@ static const char * const rk3588_otp_clocks[] = {
+
+ static const struct rockchip_data rk3588_data = {
+ .size = 0x400,
++ .read_offset = 0xc00,
+ .clks = rk3588_otp_clocks,
+ .num_clks = ARRAY_SIZE(rk3588_otp_clocks),
+ .reg_read = rk3588_otp_read,
+--
+2.45.2
diff --git a/lede/target/linux/rockchip/patches-6.12/312-03-v6.13-rk3576-otp-support.patch b/lede/target/linux/rockchip/patches-6.12/312-03-v6.13-rk3576-otp-support.patch
new file mode 100644
index 0000000000..5a4ba6771b
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/312-03-v6.13-rk3576-otp-support.patch
@@ -0,0 +1,49 @@
+Document the OTP memory found on Rockchip RK3576 SoC.
+
+The RK3576 uses the same set of clocks as the px30/rk3308
+but has one reset more, so adapt the binding to handle this
+variant as well.
+
+Signed-off-by: Heiko Stuebner
+---
+ .../bindings/nvmem/rockchip,otp.yaml | 18 ++++++++++++++++++
+ 1 file changed, 18 insertions(+)
+
+diff --git a/Documentation/devicetree/bindings/nvmem/rockchip,otp.yaml b/Documentation/devicetree/bindings/nvmem/rockchip,otp.yaml
+index a44d44b32809..dae7543a0179 100644
+--- a/Documentation/devicetree/bindings/nvmem/rockchip,otp.yaml
++++ b/Documentation/devicetree/bindings/nvmem/rockchip,otp.yaml
+@@ -14,6 +14,7 @@ properties:
+ enum:
+ - rockchip,px30-otp
+ - rockchip,rk3308-otp
++ - rockchip,rk3576-otp
+ - rockchip,rk3588-otp
+
+ reg:
+@@ -68,6 +69,23 @@ allOf:
+ items:
+ - const: phy
+
++ - if:
++ properties:
++ compatible:
++ contains:
++ enum:
++ - rockchip,rk3576-otp
++ then:
++ properties:
++ clocks:
++ minItems: 3
++ resets:
++ minItems: 2
++ reset-names:
++ items:
++ - const: otp
++ - const: apb
++
+ - if:
+ properties:
+ compatible:
+--
+2.45.2
diff --git a/lede/target/linux/rockchip/patches-6.12/312-04-v6.13-rk3576-otp-support.patch b/lede/target/linux/rockchip/patches-6.12/312-04-v6.13-rk3576-otp-support.patch
new file mode 100644
index 0000000000..e4c971aaae
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/312-04-v6.13-rk3576-otp-support.patch
@@ -0,0 +1,40 @@
+The variant works very similar to the rk3588, just with a different
+read-offset and size.
+
+Signed-off-by: Heiko Stuebner
+---
+ drivers/nvmem/rockchip-otp.c | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+diff --git a/drivers/nvmem/rockchip-otp.c b/drivers/nvmem/rockchip-otp.c
+index 3edfbfc2d722..d88f12c53242 100644
+--- a/drivers/nvmem/rockchip-otp.c
++++ b/drivers/nvmem/rockchip-otp.c
+@@ -274,6 +274,14 @@ static const struct rockchip_data px30_data = {
+ .reg_read = px30_otp_read,
+ };
+
++static const struct rockchip_data rk3576_data = {
++ .size = 0x100,
++ .read_offset = 0x700,
++ .clks = px30_otp_clocks,
++ .num_clks = ARRAY_SIZE(px30_otp_clocks),
++ .reg_read = rk3588_otp_read,
++};
++
+ static const char * const rk3588_otp_clocks[] = {
+ "otp", "apb_pclk", "phy", "arb",
+ };
+@@ -295,6 +303,10 @@ static const struct of_device_id rockchip_otp_match[] = {
+ .compatible = "rockchip,rk3308-otp",
+ .data = &px30_data,
+ },
++ {
++ .compatible = "rockchip,rk3576-otp",
++ .data = &rk3576_data,
++ },
+ {
+ .compatible = "rockchip,rk3588-otp",
+ .data = &rk3588_data,
+--
+2.45.2
diff --git a/lede/target/linux/rockchip/patches-6.12/312-05-v6.13-rk3576-otp-support.patch b/lede/target/linux/rockchip/patches-6.12/312-05-v6.13-rk3576-otp-support.patch
new file mode 100644
index 0000000000..a8d08ba180
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/312-05-v6.13-rk3576-otp-support.patch
@@ -0,0 +1,60 @@
+This adds the otp node to the rk3576 soc devicetree including the
+individual fields we know about.
+
+Signed-off-by: Heiko Stuebner
+---
+ arch/arm64/boot/dts/rockchip/rk3576.dtsi | 39 ++++++++++++++++++++++++
+ 1 file changed, 39 insertions(+)
+
+diff --git a/arch/arm64/boot/dts/rockchip/rk3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576.dtsi
+index 436232ffe4d1..c70c9dcfad82 100644
+--- a/arch/arm64/boot/dts/rockchip/rk3576.dtsi
++++ b/arch/arm64/boot/dts/rockchip/rk3576.dtsi
+@@ -1149,6 +1149,45 @@ sdhci: mmc@2a330000 {
+ status = "disabled";
+ };
+
++ otp: otp@2a580000 {
++ compatible = "rockchip,rk3576-otp";
++ reg = <0x0 0x2a580000 0x0 0x400>;
++ #address-cells = <1>;
++ #size-cells = <1>;
++ clocks = <&cru CLK_OTPC_NS>, <&cru PCLK_OTPC_NS>,
++ <&cru CLK_OTP_PHY_G>;
++ clock-names = "otp", "apb_pclk", "phy";
++ resets = <&cru SRST_OTPC_NS>, <&cru SRST_P_OTPC_NS>;
++ reset-names = "otp", "apb";
++
++ /* Data cells */
++ cpu_code: cpu-code@2 {
++ reg = <0x02 0x2>;
++ };
++ otp_cpu_version: cpu-version@5 {
++ reg = <0x05 0x1>;
++ bits = <3 3>;
++ };
++ otp_id: id@a {
++ reg = <0x0a 0x10>;
++ };
++ cpub_leakage: cpub-leakage@1e {
++ reg = <0x1e 0x1>;
++ };
++ cpul_leakage: cpul-leakage@1f {
++ reg = <0x1f 0x1>;
++ };
++ npu_leakage: npu-leakage@20 {
++ reg = <0x20 0x1>;
++ };
++ gpu_leakage: gpu-leakage@21 {
++ reg = <0x21 0x1>;
++ };
++ log_leakage: log-leakage@22 {
++ reg = <0x22 0x1>;
++ };
++ };
++
+ gic: interrupt-controller@2a701000 {
+ compatible = "arm,gic-400";
+ reg = <0x0 0x2a701000 0 0x10000>,
+--
+2.45.2
diff --git a/lede/target/linux/rockchip/patches-6.12/340-01-pwm-add-more-locking.patch b/lede/target/linux/rockchip/patches-6.12/340-01-pwm-add-more-locking.patch
new file mode 100644
index 0000000000..cb76d814ec
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/340-01-pwm-add-more-locking.patch
@@ -0,0 +1,237 @@
+Signed-off-by: Uwe Kleine-König
+---
+ drivers/pwm/core.c | 95 +++++++++++++++++++++++++++++++++++++++++----
+ include/linux/pwm.h | 13 +++++++
+ 2 files changed, 100 insertions(+), 8 deletions(-)
+
+diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
+index 6e752e148b98..b97e2ea0691d 100644
+--- a/drivers/pwm/core.c
++++ b/drivers/pwm/core.c
+@@ -31,6 +31,24 @@ static DEFINE_MUTEX(pwm_lock);
+
+ static DEFINE_IDR(pwm_chips);
+
++static void pwmchip_lock(struct pwm_chip *chip)
++{
++ if (chip->atomic)
++ spin_lock(&chip->atomic_lock);
++ else
++ mutex_lock(&chip->nonatomic_lock);
++}
++
++static void pwmchip_unlock(struct pwm_chip *chip)
++{
++ if (chip->atomic)
++ spin_unlock(&chip->atomic_lock);
++ else
++ mutex_unlock(&chip->nonatomic_lock);
++}
++
++DEFINE_GUARD(pwmchip, struct pwm_chip *, pwmchip_lock(_T), pwmchip_unlock(_T))
++
+ static void pwm_apply_debug(struct pwm_device *pwm,
+ const struct pwm_state *state)
+ {
+@@ -220,6 +238,7 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
+ int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
+ {
+ int err;
++ struct pwm_chip *chip = pwm->chip;
+
+ /*
+ * Some lowlevel driver's implementations of .apply() make use of
+@@ -230,7 +249,12 @@ int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
+ */
+ might_sleep();
+
+- if (IS_ENABLED(CONFIG_PWM_DEBUG) && pwm->chip->atomic) {
++ guard(pwmchip)(chip);
++
++ if (!chip->operational)
++ return -ENODEV;
++
++ if (IS_ENABLED(CONFIG_PWM_DEBUG) && chip->atomic) {
+ /*
+ * Catch any drivers that have been marked as atomic but
+ * that will sleep anyway.
+@@ -254,9 +278,16 @@ EXPORT_SYMBOL_GPL(pwm_apply_might_sleep);
+ */
+ int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state)
+ {
+- WARN_ONCE(!pwm->chip->atomic,
++ struct pwm_chip *chip = pwm->chip;
++
++ WARN_ONCE(!chip->atomic,
+ "sleeping PWM driver used in atomic context\n");
+
++ guard(pwmchip)(chip);
++
++ if (!chip->operational)
++ return -ENODEV;
++
+ return __pwm_apply(pwm, state);
+ }
+ EXPORT_SYMBOL_GPL(pwm_apply_atomic);
+@@ -336,6 +367,11 @@ static int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result,
+
+ guard(mutex)(&pwm_lock);
+
++ guard(pwmchip)(chip);
++
++ if (!chip->operational)
++ return -ENODEV;
++
+ return ops->capture(chip, pwm, result, timeout);
+ }
+
+@@ -368,6 +404,14 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
+ if (test_bit(PWMF_REQUESTED, &pwm->flags))
+ return -EBUSY;
+
++ /*
++ * This function is called while holding pwm_lock. As .operational only
++ * changes while holding this lock, checking it here without holding the
++ * chip lock is fine.
++ */
++ if (!chip->operational)
++ return -ENODEV;
++
+ if (!try_module_get(chip->owner))
+ return -ENODEV;
+
+@@ -396,7 +440,9 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
+ */
+ struct pwm_state state = { 0, };
+
+- err = ops->get_state(chip, pwm, &state);
++ scoped_guard(pwmchip, chip)
++ err = ops->get_state(chip, pwm, &state);
++
+ trace_pwm_get(pwm, &state, err);
+
+ if (!err)
+@@ -1020,6 +1066,7 @@ struct pwm_chip *pwmchip_alloc(struct device *parent, unsigned int npwm, size_t
+
+ chip->npwm = npwm;
+ chip->uses_pwmchip_alloc = true;
++ chip->operational = false;
+
+ pwmchip_dev = &chip->dev;
+ device_initialize(pwmchip_dev);
+@@ -1125,6 +1172,11 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
+
+ chip->owner = owner;
+
++ if (chip->atomic)
++ spin_lock_init(&chip->atomic_lock);
++ else
++ mutex_init(&chip->nonatomic_lock);
++
+ guard(mutex)(&pwm_lock);
+
+ ret = idr_alloc(&pwm_chips, chip, 0, 0, GFP_KERNEL);
+@@ -1138,6 +1190,9 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
+ if (IS_ENABLED(CONFIG_OF))
+ of_pwmchip_add(chip);
+
++ scoped_guard(pwmchip, chip)
++ chip->operational = true;
++
+ ret = device_add(&chip->dev);
+ if (ret)
+ goto err_device_add;
+@@ -1145,6 +1200,9 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
+ return 0;
+
+ err_device_add:
++ scoped_guard(pwmchip, chip)
++ chip->operational = false;
++
+ if (IS_ENABLED(CONFIG_OF))
+ of_pwmchip_remove(chip);
+
+@@ -1164,11 +1222,27 @@ void pwmchip_remove(struct pwm_chip *chip)
+ {
+ pwmchip_sysfs_unexport(chip);
+
+- if (IS_ENABLED(CONFIG_OF))
+- of_pwmchip_remove(chip);
++ scoped_guard(mutex, &pwm_lock) {
++ unsigned int i;
++
++ scoped_guard(pwmchip, chip)
++ chip->operational = false;
++
++ for (i = 0; i < chip->npwm; ++i) {
++ struct pwm_device *pwm = &chip->pwms[i];
++
++ if (test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
++ dev_alert(&chip->dev, "Freeing requested PWM #%u\n", i);
++ if (pwm->chip->ops->free)
++ pwm->chip->ops->free(pwm->chip, pwm);
++ }
++ }
++
++ if (IS_ENABLED(CONFIG_OF))
++ of_pwmchip_remove(chip);
+
+- scoped_guard(mutex, &pwm_lock)
+ idr_remove(&pwm_chips, chip->id);
++ }
+
+ device_del(&chip->dev);
+ }
+@@ -1538,12 +1612,17 @@ void pwm_put(struct pwm_device *pwm)
+
+ guard(mutex)(&pwm_lock);
+
+- if (!test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
++ /*
++ * If the chip isn't operational, PWMF_REQUESTED was already cleared. So
++ * don't warn in this case. This can only happen if a consumer called
++ * pwm_put() twice.
++ */
++ if (chip->operational && !test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
+ pr_warn("PWM device already freed\n");
+ return;
+ }
+
+- if (chip->ops->free)
++ if (chip->operational && chip->ops->free)
+ pwm->chip->ops->free(pwm->chip, pwm);
+
+ pwm->label = NULL;
+diff --git a/include/linux/pwm.h b/include/linux/pwm.h
+index 8acd60b53f58..464054a45e57 100644
+--- a/include/linux/pwm.h
++++ b/include/linux/pwm.h
+@@ -275,6 +275,9 @@ struct pwm_ops {
+ * @of_xlate: request a PWM device given a device tree PWM specifier
+ * @atomic: can the driver's ->apply() be called in atomic context
+ * @uses_pwmchip_alloc: signals if pwmchip_allow was used to allocate this chip
++ * @operational: signals if the chip can be used (or is already deregistered)
++ * @nonatomic_lock: mutex for nonatomic chips
++ * @atomic_lock: mutex for atomic chips
+ * @pwms: array of PWM devices allocated by the framework
+ */
+ struct pwm_chip {
+@@ -290,6 +293,16 @@ struct pwm_chip {
+
+ /* only used internally by the PWM framework */
+ bool uses_pwmchip_alloc;
++ bool operational;
++ union {
++ /*
++ * depending on the chip being atomic or not either the mutex or
++ * the spinlock is used. It protects .operational and
++ * synchronizes calls to the .ops->apply and .ops->get_state()
++ */
++ struct mutex nonatomic_lock;
++ struct spinlock atomic_lock;
++ };
+ struct pwm_device pwms[] __counted_by(npwm);
+ };
+
+--
+2.43.0
diff --git a/lede/target/linux/rockchip/patches-6.12/340-02-pwm-new-abstraction-for-pwm-waveforms.patch b/lede/target/linux/rockchip/patches-6.12/340-02-pwm-new-abstraction-for-pwm-waveforms.patch
new file mode 100644
index 0000000000..2aed97080b
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/340-02-pwm-new-abstraction-for-pwm-waveforms.patch
@@ -0,0 +1,412 @@
+From 17e40c25158f2505cbcdeda96624afcbab4af368 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?=
+Date: Fri, 20 Sep 2024 10:57:58 +0200
+Subject: [PATCH] pwm: New abstraction for PWM waveforms
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Up to now the configuration of a PWM setting is described exclusively by
+a struct pwm_state which contains information about period, duty_cycle,
+polarity and if the PWM is enabled. (There is another member usage_power
+which doesn't completely fit into pwm_state, I ignore it here for
+simplicity.)
+
+Instead of a polarity the new abstraction has a member duty_offset_ns
+that defines when the rising edge happens after the period start. This
+is more general, as with a pwm_state the rising edge can only happen at
+the period's start or such that the falling edge is at the end of the
+period (i.e. duty_offset_ns == 0 or duty_offset_ns == period_length_ns -
+duty_length_ns).
+
+A disabled PWM is modeled by .period_length_ns = 0. In my eyes this is a
+nice usage of that otherwise unusable setting, as it doesn't define
+anything about the future which matches the fact that consumers should
+consider the state of the output as undefined and it's just there to say
+"No further requirements about the output, you can save some power.".
+
+Further I renamed period and duty_cycle to period_length_ns and
+duty_length_ns. In the past there was confusion from time to time about
+duty_cycle being measured in nanoseconds because people expected a
+percentage of period instead. With "length_ns" as suffix the semantic
+should be more obvious to people unfamiliar with the pwm subsystem.
+period is renamed to period_length_ns for consistency.
+
+The API for consumers doesn't change yet, but lowlevel drivers can
+implement callbacks that work with pwm_waveforms instead of pwm_states.
+A new thing about these callbacks is that the calculation of hardware
+settings needed to implement a certain waveform is separated from
+actually writing these settings. The motivation for that is that this
+allows a consumer to query the hardware capabilities without actually
+modifying the hardware state.
+
+The rounding rules that are expected to be implemented in the
+round_waveform_tohw() are: First pick the biggest possible period not
+bigger than wf->period_length_ns. For that period pick the biggest
+possible duty setting not bigger than wf->duty_length_ns. Third pick the
+biggest possible offset not bigger than wf->duty_offset_ns. If the
+requested period is too small for the hardware, it's expected that a
+setting with the minimal period and duty_length_ns = duty_offset_ns = 0
+is returned and this fact is signaled by a return value of 1.
+
+Signed-off-by: Uwe Kleine-König
+Tested-by: Trevor Gamblin
+Link: https://lore.kernel.org/r/df0faa33bf9e7c9e2e5eab8d31bbf61e861bd401.1726819463.git.u.kleine-koenig@baylibre.com
+[ukleinek: Update pwm_check_rounding() to return bool instead of int.]
+Signed-off-by: Uwe Kleine-König
+---
+ drivers/pwm/core.c | 234 ++++++++++++++++++++++++++++++++++++++++----
+ include/linux/pwm.h | 36 +++++++
+ 2 files changed, 249 insertions(+), 21 deletions(-)
+
+diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
+index 5a095eb46b544f..bbe7bfdb154927 100644
+--- a/drivers/pwm/core.c
++++ b/drivers/pwm/core.c
+@@ -49,6 +49,102 @@ static void pwmchip_unlock(struct pwm_chip *chip)
+
+ DEFINE_GUARD(pwmchip, struct pwm_chip *, pwmchip_lock(_T), pwmchip_unlock(_T))
+
++static void pwm_wf2state(const struct pwm_waveform *wf, struct pwm_state *state)
++{
++ if (wf->period_length_ns) {
++ if (wf->duty_length_ns + wf->duty_offset_ns < wf->period_length_ns)
++ *state = (struct pwm_state){
++ .enabled = true,
++ .polarity = PWM_POLARITY_NORMAL,
++ .period = wf->period_length_ns,
++ .duty_cycle = wf->duty_length_ns,
++ };
++ else
++ *state = (struct pwm_state){
++ .enabled = true,
++ .polarity = PWM_POLARITY_INVERSED,
++ .period = wf->period_length_ns,
++ .duty_cycle = wf->period_length_ns - wf->duty_length_ns,
++ };
++ } else {
++ *state = (struct pwm_state){
++ .enabled = false,
++ };
++ }
++}
++
++static void pwm_state2wf(const struct pwm_state *state, struct pwm_waveform *wf)
++{
++ if (state->enabled) {
++ if (state->polarity == PWM_POLARITY_NORMAL)
++ *wf = (struct pwm_waveform){
++ .period_length_ns = state->period,
++ .duty_length_ns = state->duty_cycle,
++ .duty_offset_ns = 0,
++ };
++ else
++ *wf = (struct pwm_waveform){
++ .period_length_ns = state->period,
++ .duty_length_ns = state->period - state->duty_cycle,
++ .duty_offset_ns = state->duty_cycle,
++ };
++ } else {
++ *wf = (struct pwm_waveform){
++ .period_length_ns = 0,
++ };
++ }
++}
++
++static bool pwm_check_rounding(const struct pwm_waveform *wf,
++ const struct pwm_waveform *wf_rounded)
++{
++ if (!wf->period_length_ns)
++ return true;
++
++ if (wf->period_length_ns < wf_rounded->period_length_ns)
++ return false;
++
++ if (wf->duty_length_ns < wf_rounded->duty_length_ns)
++ return false;
++
++ if (wf->duty_offset_ns < wf_rounded->duty_offset_ns)
++ return false;
++
++ return true;
++}
++
++static int __pwm_round_waveform_tohw(struct pwm_chip *chip, struct pwm_device *pwm,
++ const struct pwm_waveform *wf, void *wfhw)
++{
++ const struct pwm_ops *ops = chip->ops;
++
++ return ops->round_waveform_tohw(chip, pwm, wf, wfhw);
++}
++
++static int __pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
++ const void *wfhw, struct pwm_waveform *wf)
++{
++ const struct pwm_ops *ops = chip->ops;
++
++ return ops->round_waveform_fromhw(chip, pwm, wfhw, wf);
++}
++
++static int __pwm_read_waveform(struct pwm_chip *chip, struct pwm_device *pwm, void *wfhw)
++{
++ const struct pwm_ops *ops = chip->ops;
++
++ return ops->read_waveform(chip, pwm, wfhw);
++}
++
++static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, const void *wfhw)
++{
++ const struct pwm_ops *ops = chip->ops;
++
++ return ops->write_waveform(chip, pwm, wfhw);
++}
++
++#define WFHWSIZE 20
++
+ static void pwm_apply_debug(struct pwm_device *pwm,
+ const struct pwm_state *state)
+ {
+@@ -182,6 +278,7 @@ static bool pwm_state_valid(const struct pwm_state *state)
+ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
+ {
+ struct pwm_chip *chip;
++ const struct pwm_ops *ops;
+ int err;
+
+ if (!pwm || !state)
+@@ -205,6 +302,7 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
+ }
+
+ chip = pwm->chip;
++ ops = chip->ops;
+
+ if (state->period == pwm->state.period &&
+ state->duty_cycle == pwm->state.duty_cycle &&
+@@ -213,18 +311,69 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
+ state->usage_power == pwm->state.usage_power)
+ return 0;
+
+- err = chip->ops->apply(chip, pwm, state);
+- trace_pwm_apply(pwm, state, err);
+- if (err)
+- return err;
++ if (ops->write_waveform) {
++ struct pwm_waveform wf;
++ char wfhw[WFHWSIZE];
+
+- pwm->state = *state;
++ BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
+
+- /*
+- * only do this after pwm->state was applied as some
+- * implementations of .get_state depend on this
+- */
+- pwm_apply_debug(pwm, state);
++ pwm_state2wf(state, &wf);
++
++ /*
++ * The rounding is wrong here for states with inverted polarity.
++ * While .apply() rounds down duty_cycle (which represents the
++ * time from the start of the period to the inner edge),
++ * .round_waveform_tohw() rounds down the time the PWM is high.
++ * Can be fixed if the need arises, until reported otherwise
++ * let's assume that consumers don't care.
++ */
++
++ err = __pwm_round_waveform_tohw(chip, pwm, &wf, &wfhw);
++ if (err) {
++ if (err > 0)
++ /*
++ * This signals an invalid request, typically
++ * the requested period (or duty_offset) is
++ * smaller than possible with the hardware.
++ */
++ return -EINVAL;
++
++ return err;
++ }
++
++ if (IS_ENABLED(CONFIG_PWM_DEBUG)) {
++ struct pwm_waveform wf_rounded;
++
++ err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded);
++ if (err)
++ return err;
++
++ if (!pwm_check_rounding(&wf, &wf_rounded))
++ dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n",
++ wf.duty_length_ns, wf.period_length_ns, wf.duty_offset_ns,
++ wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns);
++ }
++
++ err = __pwm_write_waveform(chip, pwm, &wfhw);
++ if (err)
++ return err;
++
++ pwm->state = *state;
++
++ } else {
++ err = ops->apply(chip, pwm, state);
++ trace_pwm_apply(pwm, state, err);
++ if (err)
++ return err;
++
++ pwm->state = *state;
++
++ /*
++ * only do this after pwm->state was applied as some
++ * implementations of .get_state() depend on this
++ */
++ pwm_apply_debug(pwm, state);
++ }
+
+ return 0;
+ }
+@@ -292,6 +441,41 @@ int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state)
+ }
+ EXPORT_SYMBOL_GPL(pwm_apply_atomic);
+
++static int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
++{
++ struct pwm_chip *chip = pwm->chip;
++ const struct pwm_ops *ops = chip->ops;
++ int ret = -EOPNOTSUPP;
++
++ if (ops->read_waveform) {
++ char wfhw[WFHWSIZE];
++ struct pwm_waveform wf;
++
++ BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
++
++ scoped_guard(pwmchip, chip) {
++
++ ret = __pwm_read_waveform(chip, pwm, &wfhw);
++ if (ret)
++ return ret;
++
++ ret = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf);
++ if (ret)
++ return ret;
++ }
++
++ pwm_wf2state(&wf, state);
++
++ } else if (ops->get_state) {
++ scoped_guard(pwmchip, chip)
++ ret = ops->get_state(chip, pwm, state);
++
++ trace_pwm_get(pwm, state, ret);
++ }
++
++ return ret;
++}
++
+ /**
+ * pwm_adjust_config() - adjust the current PWM config to the PWM arguments
+ * @pwm: PWM device
+@@ -435,7 +619,7 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
+ }
+ }
+
+- if (ops->get_state) {
++ if (ops->read_waveform || ops->get_state) {
+ /*
+ * Zero-initialize state because most drivers are unaware of
+ * .usage_power. The other members of state are supposed to be
+@@ -445,11 +629,7 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
+ */
+ struct pwm_state state = { 0, };
+
+- scoped_guard(pwmchip, chip)
+- err = ops->get_state(chip, pwm, &state);
+-
+- trace_pwm_get(pwm, &state, err);
+-
++ err = pwm_get_state_hw(pwm, &state);
+ if (!err)
+ pwm->state = state;
+
+@@ -1136,12 +1316,24 @@ static bool pwm_ops_check(const struct pwm_chip *chip)
+ {
+ const struct pwm_ops *ops = chip->ops;
+
+- if (!ops->apply)
+- return false;
++ if (ops->write_waveform) {
++ if (!ops->round_waveform_tohw ||
++ !ops->round_waveform_fromhw ||
++ !ops->write_waveform)
++ return false;
+
+- if (IS_ENABLED(CONFIG_PWM_DEBUG) && !ops->get_state)
+- dev_warn(pwmchip_parent(chip),
+- "Please implement the .get_state() callback\n");
++ if (WFHWSIZE < ops->sizeof_wfhw) {
++ dev_warn(pwmchip_parent(chip), "WFHWSIZE < %zu\n", ops->sizeof_wfhw);
++ return false;
++ }
++ } else {
++ if (!ops->apply)
++ return false;
++
++ if (IS_ENABLED(CONFIG_PWM_DEBUG) && !ops->get_state)
++ dev_warn(pwmchip_parent(chip),
++ "Please implement the .get_state() callback\n");
++ }
+
+ return true;
+ }
+diff --git a/include/linux/pwm.h b/include/linux/pwm.h
+index 3ea73e075abe87..d8cfe1c9b19d83 100644
+--- a/include/linux/pwm.h
++++ b/include/linux/pwm.h
+@@ -49,6 +49,31 @@ enum {
+ PWMF_EXPORTED = 1,
+ };
+
++/**
++ * struct pwm_waveform - description of a PWM waveform
++ * @period_length_ns: PWM period
++ * @duty_length_ns: PWM duty cycle
++ * @duty_offset_ns: offset of the rising edge from the period's start
++ *
++ * This is a representation of a PWM waveform alternative to struct pwm_state
++ * below. It's more expressive than struct pwm_state as it contains a
++ * duty_offset_ns and so can represent offsets other than zero (with .polarity =
++ * PWM_POLARITY_NORMAL) and period - duty_cycle (.polarity =
++ * PWM_POLARITY_INVERSED).
++ *
++ * Note there is no explicit bool for enabled. A "disabled" PWM is represented
++ * by .period_length_ns = 0. Note further that the behaviour of a "disabled" PWM
++ * is undefined. Depending on the hardware's capabilities it might drive the
++ * active or inactive level, go high-z or even continue to toggle.
++ *
++ * The unit for all three members is nanoseconds.
++ */
++struct pwm_waveform {
++ u64 period_length_ns;
++ u64 duty_length_ns;
++ u64 duty_offset_ns;
++};
++
+ /*
+ * struct pwm_state - state of a PWM channel
+ * @period: PWM period (in nanoseconds)
+@@ -259,6 +284,17 @@ struct pwm_ops {
+ void (*free)(struct pwm_chip *chip, struct pwm_device *pwm);
+ int (*capture)(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_capture *result, unsigned long timeout);
++
++ size_t sizeof_wfhw;
++ int (*round_waveform_tohw)(struct pwm_chip *chip, struct pwm_device *pwm,
++ const struct pwm_waveform *wf, void *wfhw);
++ int (*round_waveform_fromhw)(struct pwm_chip *chip, struct pwm_device *pwm,
++ const void *wfhw, struct pwm_waveform *wf);
++ int (*read_waveform)(struct pwm_chip *chip, struct pwm_device *pwm,
++ void *wfhw);
++ int (*write_waveform)(struct pwm_chip *chip, struct pwm_device *pwm,
++ const void *wfhw);
++
+ int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state);
+ int (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm,
diff --git a/lede/target/linux/rockchip/patches-6.12/340-03-pwm-provide-new-consumer-API-functions-for-waveforms.patch b/lede/target/linux/rockchip/patches-6.12/340-03-pwm-provide-new-consumer-API-functions-for-waveforms.patch
new file mode 100644
index 0000000000..9efb3a6dbb
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/340-03-pwm-provide-new-consumer-API-functions-for-waveforms.patch
@@ -0,0 +1,327 @@
+From 6c5126c6406d1c31e91f5b925c621c1c785366be Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?=
+Date: Fri, 20 Sep 2024 10:57:59 +0200
+Subject: [PATCH] pwm: Provide new consumer API functions for waveforms
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Provide API functions for consumers to work with waveforms.
+
+Note that one relevant difference between pwm_get_state() and
+pwm_get_waveform*() is that the latter yields the actually configured
+hardware state, while the former yields the last state passed to
+pwm_apply*() and so doesn't account for hardware specific rounding.
+
+Signed-off-by: Uwe Kleine-König
+Tested-by: Trevor Gamblin
+Link: https://lore.kernel.org/r/6c97d27682853f603e18e9196043886dd671845d.1726819463.git.u.kleine-koenig@baylibre.com
+Signed-off-by: Uwe Kleine-König
+---
+ drivers/pwm/core.c | 261 ++++++++++++++++++++++++++++++++++++++++++++
+ include/linux/pwm.h | 6 +-
+ 2 files changed, 266 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
+index bbe7bfdb154927..038f17dd275798 100644
+--- a/drivers/pwm/core.c
++++ b/drivers/pwm/core.c
+@@ -49,6 +49,30 @@ static void pwmchip_unlock(struct pwm_chip *chip)
+
+ DEFINE_GUARD(pwmchip, struct pwm_chip *, pwmchip_lock(_T), pwmchip_unlock(_T))
+
++static bool pwm_wf_valid(const struct pwm_waveform *wf)
++{
++ /*
++ * For now restrict waveforms to period_length_ns <= S64_MAX to provide
++ * some space for future extensions. One possibility is to simplify
++ * representing waveforms with inverted polarity using negative values
++ * somehow.
++ */
++ if (wf->period_length_ns > S64_MAX)
++ return false;
++
++ if (wf->duty_length_ns > wf->period_length_ns)
++ return false;
++
++ /*
++ * .duty_offset_ns is supposed to be smaller than .period_length_ns, apart
++ * from the corner case .duty_offset_ns == 0 && .period_length_ns == 0.
++ */
++ if (wf->duty_offset_ns && wf->duty_offset_ns >= wf->period_length_ns)
++ return false;
++
++ return true;
++}
++
+ static void pwm_wf2state(const struct pwm_waveform *wf, struct pwm_state *state)
+ {
+ if (wf->period_length_ns) {
+@@ -95,6 +119,29 @@ static void pwm_state2wf(const struct pwm_state *state, struct pwm_waveform *wf)
+ }
+ }
+
++static int pwmwfcmp(const struct pwm_waveform *a, const struct pwm_waveform *b)
++{
++ if (a->period_length_ns > b->period_length_ns)
++ return 1;
++
++ if (a->period_length_ns < b->period_length_ns)
++ return -1;
++
++ if (a->duty_length_ns > b->duty_length_ns)
++ return 1;
++
++ if (a->duty_length_ns < b->duty_length_ns)
++ return -1;
++
++ if (a->duty_offset_ns > b->duty_offset_ns)
++ return 1;
++
++ if (a->duty_offset_ns < b->duty_offset_ns)
++ return -1;
++
++ return 0;
++}
++
+ static bool pwm_check_rounding(const struct pwm_waveform *wf,
+ const struct pwm_waveform *wf_rounded)
+ {
+@@ -145,6 +192,220 @@ static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, c
+
+ #define WFHWSIZE 20
+
++/**
++ * pwm_round_waveform_might_sleep - Query hardware capabilities
++ * Cannot be used in atomic context.
++ * @pwm: PWM device
++ * @wf: waveform to round and output parameter
++ *
++ * Typically a given waveform cannot be implemented exactly by hardware, e.g.
++ * because hardware only supports coarse period resolution or no duty_offset.
++ * This function returns the actually implemented waveform if you pass wf to
++ * pwm_set_waveform_might_sleep now.
++ *
++ * Note however that the world doesn't stop turning when you call it, so when
++ * doing
++ *
++ * pwm_round_waveform_might_sleep(mypwm, &wf);
++ * pwm_set_waveform_might_sleep(mypwm, &wf, true);
++ *
++ * the latter might fail, e.g. because an input clock changed its rate between
++ * these two calls and the waveform determined by
++ * pwm_round_waveform_might_sleep() cannot be implemented any more.
++ *
++ * Returns 0 on success, 1 if there is no valid hardware configuration matching
++ * the input waveform under the PWM rounding rules or a negative errno.
++ */
++int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf)
++{
++ struct pwm_chip *chip = pwm->chip;
++ const struct pwm_ops *ops = chip->ops;
++ struct pwm_waveform wf_req = *wf;
++ char wfhw[WFHWSIZE];
++ int ret_tohw, ret_fromhw;
++
++ BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
++
++ if (!pwm_wf_valid(wf))
++ return -EINVAL;
++
++ guard(pwmchip)(chip);
++
++ if (!chip->operational)
++ return -ENODEV;
++
++ ret_tohw = __pwm_round_waveform_tohw(chip, pwm, wf, wfhw);
++ if (ret_tohw < 0)
++ return ret_tohw;
++
++ if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_tohw > 1)
++ dev_err(&chip->dev, "Unexpected return value from __pwm_round_waveform_tohw: requested %llu/%llu [+%llu], return value %d\n",
++ wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, ret_tohw);
++
++ ret_fromhw = __pwm_round_waveform_fromhw(chip, pwm, wfhw, wf);
++ if (ret_fromhw < 0)
++ return ret_fromhw;
++
++ if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_fromhw > 0)
++ dev_err(&chip->dev, "Unexpected return value from __pwm_round_waveform_fromhw: requested %llu/%llu [+%llu], return value %d\n",
++ wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, ret_tohw);
++
++ if (IS_ENABLED(CONFIG_PWM_DEBUG) &&
++ ret_tohw == 0 && !pwm_check_rounding(&wf_req, wf))
++ dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n",
++ wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns,
++ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns);
++
++ return ret_tohw;
++}
++EXPORT_SYMBOL_GPL(pwm_round_waveform_might_sleep);
++
++/**
++ * pwm_get_waveform_might_sleep - Query hardware about current configuration
++ * Cannot be used in atomic context.
++ * @pwm: PWM device
++ * @wf: output parameter
++ *
++ * Stores the current configuration of the PWM in @wf. Note this is the
++ * equivalent of pwm_get_state_hw() (and not pwm_get_state()) for pwm_waveform.
++ */
++int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf)
++{
++ struct pwm_chip *chip = pwm->chip;
++ const struct pwm_ops *ops = chip->ops;
++ char wfhw[WFHWSIZE];
++ int err;
++
++ BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
++
++ guard(pwmchip)(chip);
++
++ if (!chip->operational)
++ return -ENODEV;
++
++ err = __pwm_read_waveform(chip, pwm, &wfhw);
++ if (err)
++ return err;
++
++ return __pwm_round_waveform_fromhw(chip, pwm, &wfhw, wf);
++}
++EXPORT_SYMBOL_GPL(pwm_get_waveform_might_sleep);
++
++/* Called with the pwmchip lock held */
++static int __pwm_set_waveform(struct pwm_device *pwm,
++ const struct pwm_waveform *wf,
++ bool exact)
++{
++ struct pwm_chip *chip = pwm->chip;
++ const struct pwm_ops *ops = chip->ops;
++ char wfhw[WFHWSIZE];
++ struct pwm_waveform wf_rounded;
++ int err;
++
++ BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
++
++ if (!pwm_wf_valid(wf))
++ return -EINVAL;
++
++ err = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw);
++ if (err)
++ return err;
++
++ if ((IS_ENABLED(CONFIG_PWM_DEBUG) || exact) && wf->period_length_ns) {
++ err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded);
++ if (err)
++ return err;
++
++ if (IS_ENABLED(CONFIG_PWM_DEBUG) && !pwm_check_rounding(wf, &wf_rounded))
++ dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n",
++ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
++ wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns);
++
++ if (exact && pwmwfcmp(wf, &wf_rounded)) {
++ dev_dbg(&chip->dev, "Requested no rounding, but %llu/%llu [+%llu] -> %llu/%llu [+%llu]\n",
++ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
++ wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns);
++
++ return 1;
++ }
++ }
++
++ err = __pwm_write_waveform(chip, pwm, &wfhw);
++ if (err)
++ return err;
++
++ /* update .state */
++ pwm_wf2state(wf, &pwm->state);
++
++ if (IS_ENABLED(CONFIG_PWM_DEBUG) && ops->read_waveform && wf->period_length_ns) {
++ struct pwm_waveform wf_set;
++
++ err = __pwm_read_waveform(chip, pwm, &wfhw);
++ if (err)
++ /* maybe ignore? */
++ return err;
++
++ err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_set);
++ if (err)
++ /* maybe ignore? */
++ return err;
++
++ if (pwmwfcmp(&wf_set, &wf_rounded) != 0)
++ dev_err(&chip->dev,
++ "Unexpected setting: requested %llu/%llu [+%llu], expected %llu/%llu [+%llu], set %llu/%llu [+%llu]\n",
++ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
++ wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns,
++ wf_set.duty_length_ns, wf_set.period_length_ns, wf_set.duty_offset_ns);
++ }
++ return 0;
++}
++
++/**
++ * pwm_set_waveform_might_sleep - Apply a new waveform
++ * Cannot be used in atomic context.
++ * @pwm: PWM device
++ * @wf: The waveform to apply
++ * @exact: If true no rounding is allowed
++ *
++ * Typically a requested waveform cannot be implemented exactly, e.g. because
++ * you requested .period_length_ns = 100 ns, but the hardware can only set
++ * periods that are a multiple of 8.5 ns. With that hardware passing exact =
++ * true results in pwm_set_waveform_might_sleep() failing and returning 1. If
++ * exact = false you get a period of 93.5 ns (i.e. the biggest period not bigger
++ * than the requested value).
++ * Note that even with exact = true, some rounding by less than 1 is
++ * possible/needed. In the above example requesting .period_length_ns = 94 and
++ * exact = true, you get the hardware configured with period = 93.5 ns.
++ */
++int pwm_set_waveform_might_sleep(struct pwm_device *pwm,
++ const struct pwm_waveform *wf, bool exact)
++{
++ struct pwm_chip *chip = pwm->chip;
++ int err;
++
++ might_sleep();
++
++ guard(pwmchip)(chip);
++
++ if (!chip->operational)
++ return -ENODEV;
++
++ if (IS_ENABLED(CONFIG_PWM_DEBUG) && chip->atomic) {
++ /*
++ * Catch any drivers that have been marked as atomic but
++ * that will sleep anyway.
++ */
++ non_block_start();
++ err = __pwm_set_waveform(pwm, wf, exact);
++ non_block_end();
++ } else {
++ err = __pwm_set_waveform(pwm, wf, exact);
++ }
++
++ return err;
++}
++EXPORT_SYMBOL_GPL(pwm_set_waveform_might_sleep);
++
+ static void pwm_apply_debug(struct pwm_device *pwm,
+ const struct pwm_state *state)
+ {
+diff --git a/include/linux/pwm.h b/include/linux/pwm.h
+index d8cfe1c9b19d83..c3d9ddeafa65e1 100644
+--- a/include/linux/pwm.h
++++ b/include/linux/pwm.h
+@@ -358,7 +358,11 @@ static inline void pwmchip_set_drvdata(struct pwm_chip *chip, void *data)
+ }
+
+ #if IS_ENABLED(CONFIG_PWM)
+-/* PWM user APIs */
++
++/* PWM consumer APIs */
++int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf);
++int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf);
++int pwm_set_waveform_might_sleep(struct pwm_device *pwm, const struct pwm_waveform *wf, bool exact);
+ int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state);
+ int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state);
+ int pwm_adjust_config(struct pwm_device *pwm);
diff --git a/lede/target/linux/rockchip/patches-6.12/341-01-dt-bindings-pinctrl-rockchip-increase-mas-amount-of-device-functions.patch b/lede/target/linux/rockchip/patches-6.12/341-01-dt-bindings-pinctrl-rockchip-increase-mas-amount-of-device-functions.patch
new file mode 100644
index 0000000000..562b48e697
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/341-01-dt-bindings-pinctrl-rockchip-increase-mas-amount-of-device-functions.patch
@@ -0,0 +1,21 @@
+Signed-off-by: Nicolas Frattaroli
+---
+ Documentation/devicetree/bindings/pinctrl/rockchip,pinctrl.yaml | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/Documentation/devicetree/bindings/pinctrl/rockchip,pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/rockchip,pinctrl.yaml
+index 960758dc417f7405010fab067bfbf6f5c4704179..125af766b99297dc229db158846daea974dda28e 100644
+--- a/Documentation/devicetree/bindings/pinctrl/rockchip,pinctrl.yaml
++++ b/Documentation/devicetree/bindings/pinctrl/rockchip,pinctrl.yaml
+@@ -135,7 +135,7 @@ additionalProperties:
+ description:
+ Pin bank index.
+ - minimum: 0
+- maximum: 13
++ maximum: 14
+ description:
+ Mux 0 means GPIO and mux 1 to N means
+ the specific device function.
+
+--
+2.49.0
diff --git a/lede/target/linux/rockchip/patches-6.12/341-02-dt-bindings-pwm-add-a-new-binding-for-rockchip-rk3576-pwm.patch b/lede/target/linux/rockchip/patches-6.12/341-02-dt-bindings-pwm-add-a-new-binding-for-rockchip-rk3576-pwm.patch
new file mode 100644
index 0000000000..332b358b1a
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/341-02-dt-bindings-pwm-add-a-new-binding-for-rockchip-rk3576-pwm.patch
@@ -0,0 +1,127 @@
+Signed-off-by: Nicolas Frattaroli
+---
+ .../bindings/pwm/rockchip,rk3576-pwm.yaml | 94 ++++++++++++++++++++++
+ MAINTAINERS | 7 ++
+ 2 files changed, 101 insertions(+)
+
+diff --git a/Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml b/Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml
+new file mode 100644
+index 0000000000000000000000000000000000000000..143d4df5df8fa89d508faca5ddf67603fb7cb3f5
+--- /dev/null
++++ b/Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml
+@@ -0,0 +1,94 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/pwm/rockchip,rk3576-pwm.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Rockchip PWMv4 controller
++
++maintainers:
++ - Nicolas Frattaroli
++
++description: |
++ The Rockchip PWMv4 controller is a PWM controller found on several Rockchip
++ SoCs, such as the RK3576.
++
++ It supports both generating and capturing PWM signals.
++
++allOf:
++ - $ref: pwm.yaml#
++
++properties:
++ compatible:
++ items:
++ - const: rockchip,rk3576-pwm
++
++ reg:
++ maxItems: 1
++
++ clocks:
++ minItems: 2
++ items:
++ - description: Used to derive the PWM signal.
++ - description: Used as the APB bus clock.
++ - description: Used as an added alternative to derive the PWM signal.
++
++ clock-names:
++ minItems: 2
++ items:
++ - const: pwm
++ - const: pclk
++ - const: osc
++
++ interrupts:
++ maxItems: 1
++
++ "#pwm-cells":
++ const: 3
++
++required:
++ - compatible
++ - reg
++ - clocks
++ - clock-names
++ - interrupts
++
++additionalProperties: false
++
++examples:
++ - |
++ #include
++ #include
++ #include
++
++ soc {
++ #address-cells = <2>;
++ #size-cells = <2>;
++
++ pwm@2add0000 {
++ compatible = "rockchip,rk3576-pwm";
++ reg = <0x0 0x2add0000 0x0 0x1000>;
++ clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, <&cru CLK_OSC_PWM1>;
++ clock-names = "pwm", "pclk", "osc";
++ interrupts = ;
++ #pwm-cells = <3>;
++ };
++ };
++ - |
++ #include
++ #include
++ #include
++
++ soc {
++ #address-cells = <2>;
++ #size-cells = <2>;
++
++ pwm@2ade3000 {
++ compatible = "rockchip,rk3576-pwm";
++ reg = <0x0 0x2ade3000 0x0 0x1000>;
++ clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>;
++ clock-names = "pwm", "pclk";
++ interrupts = ;
++ #pwm-cells = <3>;
++ };
++ };
+diff --git a/MAINTAINERS b/MAINTAINERS
+index 96b82704950184bd71623ff41fc4df31e4c7fe87..407179d2a90dd49800f2bb5770a1280c5afebb5a 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -20885,6 +20885,13 @@ F: Documentation/userspace-api/media/v4l/metafmt-rkisp1.rst
+ F: drivers/media/platform/rockchip/rkisp1
+ F: include/uapi/linux/rkisp1-config.h
+
++ROCKCHIP MFPWM
++M: Nicolas Frattaroli
++L: linux-rockchip@lists.infradead.org
++L: linux-pwm@vger.kernel.org
++S: Maintained
++F: Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml
++
+ ROCKCHIP RK3568 RANDOM NUMBER GENERATOR SUPPORT
+ M: Daniel Golle
+ M: Aurelien Jarno
+
+--
+2.49.0
diff --git a/lede/target/linux/rockchip/patches-6.12/341-03-soc-rockchip-add-utils-header-for-things-shared-across-drivers.patch b/lede/target/linux/rockchip/patches-6.12/341-03-soc-rockchip-add-utils-header-for-things-shared-across-drivers.patch
new file mode 100644
index 0000000000..83ad5b87f8
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/341-03-soc-rockchip-add-utils-header-for-things-shared-across-drivers.patch
@@ -0,0 +1,90 @@
+Signed-off-by: Nicolas Frattaroli
+---
+ include/soc/rockchip/utils.h | 76 ++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 76 insertions(+)
+
+diff --git a/include/soc/rockchip/utils.h b/include/soc/rockchip/utils.h
+new file mode 100644
+index 0000000000000000000000000000000000000000..3349069e75ff51ebd7a22089af796feafd227ffb
+--- /dev/null
++++ b/include/soc/rockchip/utils.h
+@@ -0,0 +1,76 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++/*
++ * Copyright (c) 2025 Collabora Ltd.
++ *
++ * Utility types, inline functions, and macros that are used across several
++ * Rockchip-specific drivers.
++ *
++ * Authors:
++ * Nicolas Frattaroli
++ */
++
++#ifndef __SOC_ROCKCHIP_UTILS_H__
++#define __SOC_ROCKCHIP_UTILS_H__
++
++#include
++#include
++#include
++
++/*
++ * Incoming macro basilisks, stare directly at them at your own peril.
++ * As a gentle reminder to help with code comprehension: BUILD_BUG_ON_ZERO
++ * is confusingly named; it's a version of BUILD_BUG_ON that evaluates to zero
++ * if it does not trigger, i.e. the assertion within the macro still checks
++ * for a truthy value, not zero.
++ */
++
++/**
++ * REG_UPDATE_WE - generate a register write value with a write-enable mask
++ * @_val: unshifted value we wish to update between @_low and @_high
++ * @_low: index of the low bit of the bit range we want to update
++ * @_high: index of the high bit of the bit range we want to update
++ *
++ * This macro statically generates a value consisting of @_val shifted to the
++ * left by @_low, and a write-enable mask in the upper 16 bits of the value
++ * that sets bit ``i << 16`` to ``1`` if bit ``i`` is within the @_low to @_high
++ * range. Only up to bit (@_high - @_low) of @_val is used for safety, i.e.
++ * trying to write a value that doesn't fit in the specified range will simply
++ * truncate it.
++ *
++ * This is useful for some hardware, like some of Rockchip's registers, where
++ * a 32-bit width register is divided into a value low half, and a write enable
++ * high half. Bits in the low half are only update if the corresponding bit in
++ * the high half is ``1``, allowing for lock-free atomic updates of a register.
++ *
++ * This macro replaces the venerable ``HIWORD_UPDATE``, which is copied and
++ * pasted in slightly different forms across many different Rockchip drivers.
++ * Before switching drivers to use it, familiarise yourself with the semantics
++ * of your specific ``HIWORD_UPDATE`` compared to this function-like macro's
++ * semantics.
++ *
++ * Return: the value, shifted into place, with the required write-enable bits
++ */
++#define REG_UPDATE_WE(_val, _low, _high) ( \
++ BUILD_BUG_ON_ZERO(const_true((_low) > (_high))) + \
++ BUILD_BUG_ON_ZERO(const_true((_high) > 15)) + \
++ BUILD_BUG_ON_ZERO(const_true((_low) < 0)) + \
++ BUILD_BUG_ON_ZERO(const_true((u64) (_val) > U16_MAX)) + \
++ ((_val & GENMASK((_high) - (_low), 0)) << (_low) | \
++ (GENMASK((_high), (_low)) << 16)))
++
++/**
++ * REG_UPDATE_BIT_WE - update a bit with a write-enable mask
++ * @__val: new value of the bit, either ``0`` 0r ``1``
++ * @__bit: bit index to modify, 0 <= @__bit < 16.
++ *
++ * This is like REG_UPDATE_WE() but only modifies a single bit, thereby making
++ * invocation easier by avoiding having to pass a repeated value.
++ *
++ * Return: a value with bit @__bit set to @__val and @__bit << 16 set to ``1``
++ */
++#define REG_UPDATE_BIT_WE(__val, __bit) ( \
++ BUILD_BUG_ON_ZERO(const_true((__val) > 1)) + \
++ BUILD_BUG_ON_ZERO(const_true((__val) < 0)) + \
++ REG_UPDATE_WE((__val), (__bit), (__bit)))
++
++#endif /* __SOC_ROCKCHIP_UTILS_H__ */
+
+--
+2.49.0
diff --git a/lede/target/linux/rockchip/patches-6.12/341-04-soc-rockchip-add-mfpwm-driver.patch b/lede/target/linux/rockchip/patches-6.12/341-04-soc-rockchip-add-mfpwm-driver.patch
new file mode 100644
index 0000000000..405323e030
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/341-04-soc-rockchip-add-mfpwm-driver.patch
@@ -0,0 +1,1181 @@
+Signed-off-by: Nicolas Frattaroli
+---
+ MAINTAINERS | 2 +
+ drivers/soc/rockchip/Kconfig | 13 +
+ drivers/soc/rockchip/Makefile | 1 +
+ drivers/soc/rockchip/mfpwm.c | 608 ++++++++++++++++++++++++++++++++++++++++++
+ include/soc/rockchip/mfpwm.h | 505 +++++++++++++++++++++++++++++++++++
+ 5 files changed, 1129 insertions(+)
+
+diff --git a/MAINTAINERS b/MAINTAINERS
+index 407179d2a90dd49800f2bb5770a1280c5afebb5a..e6a9347be1e7889089e1d9e655cb23c2d8399b40 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -20891,6 +20891,8 @@ L: linux-rockchip@lists.infradead.org
+ L: linux-pwm@vger.kernel.org
+ S: Maintained
+ F: Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml
++F: drivers/soc/rockchip/mfpwm.c
++F: include/soc/rockchip/mfpwm.h
+
+ ROCKCHIP RK3568 RANDOM NUMBER GENERATOR SUPPORT
+ M: Daniel Golle
+diff --git a/drivers/soc/rockchip/Kconfig b/drivers/soc/rockchip/Kconfig
+index 785f60c6f3ad1a09f517e69a69726a8178bed168..4e1e4926c514a5a2c4d4caf8cf9809a098badc7d 100644
+--- a/drivers/soc/rockchip/Kconfig
++++ b/drivers/soc/rockchip/Kconfig
+@@ -30,4 +30,17 @@ config ROCKCHIP_DTPM
+ on this platform. That will create all the power capping capable
+ devices.
+
++config ROCKCHIP_MFPWM
++ tristate "Rockchip multi-function PWM controller"
++ depends on OF
++ depends on HAS_IOMEM
++ help
++ Some Rockchip SoCs, such as the RK3576, use a PWM controller that has
++ several different functions, such as generating PWM waveforms but also
++ counting waveforms.
++
++ This driver manages the overall device, and selects between different
++ functionalities at runtime as needed, with drivers for them
++ implemented in their respective subsystems.
++
+ endif
+diff --git a/drivers/soc/rockchip/Makefile b/drivers/soc/rockchip/Makefile
+index 23d414433c8c58557effc214337ec8e6ff17a461..ba12dbd01ac794910d9407c268e89071cd2b3139 100644
+--- a/drivers/soc/rockchip/Makefile
++++ b/drivers/soc/rockchip/Makefile
+@@ -5,3 +5,4 @@
+ obj-$(CONFIG_ROCKCHIP_GRF) += grf.o
+ obj-$(CONFIG_ROCKCHIP_IODOMAIN) += io-domain.o
+ obj-$(CONFIG_ROCKCHIP_DTPM) += dtpm.o
++obj-$(CONFIG_ROCKCHIP_MFPWM) += mfpwm.o
+diff --git a/drivers/soc/rockchip/mfpwm.c b/drivers/soc/rockchip/mfpwm.c
+new file mode 100644
+index 0000000000000000000000000000000000000000..9331c530f0581573e2b74f62a6622b8625c5b2c5
+--- /dev/null
++++ b/drivers/soc/rockchip/mfpwm.c
+@@ -0,0 +1,608 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (c) 2025 Collabora Ltd.
++ *
++ * A driver to manage all the different functionalities exposed by Rockchip's
++ * PWMv4 hardware.
++ *
++ * This driver is chiefly focused on guaranteeing non-concurrent operation
++ * between the different device functions, as well as setting the clocks.
++ * It registers the device function platform devices, e.g. PWM output or
++ * PWM capture.
++ *
++ * Authors:
++ * Nicolas Frattaroli
++ */
++
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++
++/**
++ * struct rockchip_mfpwm - private mfpwm driver instance state struct
++ * @pdev: pointer to this instance's &struct platform_device
++ * @base: pointer to the memory mapped registers of this device
++ * @pwm_clk: pointer to the PLL clock the PWM signal may be derived from
++ * @osc_clk: pointer to the fixed crystal the PWM signal may be derived from
++ * @chosen_clk: is one of either @pwm_clk or @osc_clk, depending on choice.
++ * May only be swapped out while holding @state_lock.
++ * @pclk: pointer to the APB bus clock needed for mmio register access
++ * @pwm_dev: pointer to the &struct platform_device of the pwm output driver
++ * @counter_dev: pointer to the &struct platform_device of the counter driver
++ * @active_func: pointer to the currently active device function, or %NULL if no
++ * device function is currently actively using any of the shared
++ * resources. May only be checked/modified with @state_lock held.
++ * @acquire_cnt: number of times @active_func has currently mfpwm_acquire()'d
++ * it. Must only be checked or modified while holding @state_lock.
++ * @pwmclk_enable_cnt: number of times @active_func has enabled the pwmclk sans
++ * disabling it. Must only be checked or modified while
++ * holding @state_lock. Only exists to fix a splat on mfpwm
++ * driver remove.
++ * @state_lock: this lock is held while either the active device function, the
++ * enable register, or the chosen clock is being changed.
++ * @irq: the IRQ number of this device
++ */
++struct rockchip_mfpwm {
++ struct platform_device *pdev;
++ void __iomem *base;
++ struct clk *pwm_clk;
++ struct clk *osc_clk;
++ struct clk *chosen_clk;
++ struct clk *pclk;
++ struct platform_device *pwm_dev;
++ struct platform_device *counter_dev;
++ struct rockchip_mfpwm_func *active_func;
++ unsigned int acquire_cnt;
++ unsigned int pwmclk_enable_cnt;
++ spinlock_t state_lock;
++ int irq;
++};
++
++static atomic_t subdev_id = ATOMIC_INIT(0);
++
++static inline struct rockchip_mfpwm *to_rockchip_mfpwm(struct platform_device *pdev)
++{
++ return platform_get_drvdata(pdev);
++}
++
++unsigned long mfpwm_clk_get_rate(struct rockchip_mfpwm *mfpwm)
++{
++ if (!mfpwm || !mfpwm->chosen_clk)
++ return 0;
++
++ return clk_get_rate(mfpwm->chosen_clk);
++}
++EXPORT_SYMBOL_NS_GPL(mfpwm_clk_get_rate, ROCKCHIP_MFPWM);
++
++static int mfpwm_check_pwmf(const struct rockchip_mfpwm_func *pwmf,
++ const char *fname)
++{
++ if (IS_ERR_OR_NULL(pwmf)) {
++ WARN(1, "called %s with an erroneous handle, no effect\n",
++ fname);
++ return -EINVAL;
++ }
++
++ if (IS_ERR_OR_NULL(pwmf->parent)) {
++ WARN(1, "called %s with an erroneous mfpwm_func parent, no effect\n",
++ fname);
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++__attribute__((nonnull))
++static bool mfpwm_pwmf_is_active_pwmf(const struct rockchip_mfpwm_func *pwmf)
++{
++ if (pwmf->parent->active_func) {
++ if (pwmf->parent->active_func->id == pwmf->id)
++ return true;
++ }
++
++ return false;
++}
++
++int mfpwm_pwmclk_enable(struct rockchip_mfpwm_func *pwmf)
++{
++ unsigned long flags;
++ int ret;
++
++ ret = mfpwm_check_pwmf(pwmf, "mfpwm_pwmclk_enable");
++ if (ret)
++ return ret;
++
++ spin_lock_irqsave(&pwmf->parent->state_lock, flags);
++ if (mfpwm_pwmf_is_active_pwmf(pwmf)) {
++ ret = clk_enable(pwmf->parent->chosen_clk);
++ pwmf->parent->pwmclk_enable_cnt++;
++ } else {
++ ret = -EBUSY;
++ }
++
++ spin_unlock_irqrestore(&pwmf->parent->state_lock, flags);
++
++ return ret;
++}
++EXPORT_SYMBOL_NS_GPL(mfpwm_pwmclk_enable, ROCKCHIP_MFPWM);
++
++void mfpwm_pwmclk_disable(struct rockchip_mfpwm_func *pwmf)
++{
++ unsigned long flags;
++
++ if (mfpwm_check_pwmf(pwmf, "mfpwm_pwmclk_enable"))
++ return;
++
++ spin_lock_irqsave(&pwmf->parent->state_lock, flags);
++ if (mfpwm_pwmf_is_active_pwmf(pwmf)) {
++ clk_disable(pwmf->parent->chosen_clk);
++ pwmf->parent->pwmclk_enable_cnt--;
++ }
++ spin_unlock_irqrestore(&pwmf->parent->state_lock, flags);
++}
++EXPORT_SYMBOL_NS_GPL(mfpwm_pwmclk_disable, ROCKCHIP_MFPWM);
++
++__attribute__((nonnull))
++static int mfpwm_do_acquire(struct rockchip_mfpwm_func *pwmf)
++{
++ struct rockchip_mfpwm *mfpwm = pwmf->parent;
++ unsigned int cnt;
++
++ if (mfpwm->active_func && pwmf->id != mfpwm->active_func->id)
++ return -EBUSY;
++
++ if (!mfpwm->active_func)
++ mfpwm->active_func = pwmf;
++
++ if (!check_add_overflow(mfpwm->acquire_cnt, 1, &cnt)) {
++ mfpwm->acquire_cnt = cnt;
++ } else {
++ WARN(1, "prevented acquire counter overflow in %s\n", __func__);
++ return -EOVERFLOW;
++ }
++
++ dev_dbg(&mfpwm->pdev->dev, "%d acquired mfpwm, acquires now at %u\n",
++ pwmf->id, mfpwm->acquire_cnt);
++
++ return clk_enable(mfpwm->pclk);
++}
++
++int mfpwm_acquire(struct rockchip_mfpwm_func *pwmf)
++{
++ struct rockchip_mfpwm *mfpwm;
++ unsigned long flags;
++ int ret = 0;
++
++ ret = mfpwm_check_pwmf(pwmf, "mfpwm_acquire");
++ if (ret)
++ return ret;
++
++ mfpwm = pwmf->parent;
++ dev_dbg(&mfpwm->pdev->dev, "%d is attempting to acquire\n", pwmf->id);
++
++ if (!spin_trylock_irqsave(&mfpwm->state_lock, flags))
++ return -EBUSY;
++
++ ret = mfpwm_do_acquire(pwmf);
++
++ spin_unlock_irqrestore(&mfpwm->state_lock, flags);
++
++ return ret;
++}
++EXPORT_SYMBOL_NS_GPL(mfpwm_acquire, ROCKCHIP_MFPWM);
++
++__attribute__((nonnull))
++static void mfpwm_do_release(const struct rockchip_mfpwm_func *pwmf)
++{
++ struct rockchip_mfpwm *mfpwm = pwmf->parent;
++
++ if (!mfpwm->active_func)
++ return;
++
++ if (mfpwm->active_func->id != pwmf->id)
++ return;
++
++ /*
++ * No need to check_sub_overflow here, !mfpwm->active_func above catches
++ * this type of problem already.
++ */
++ mfpwm->acquire_cnt--;
++
++ if (!mfpwm->acquire_cnt)
++ mfpwm->active_func = NULL;
++
++ clk_disable(mfpwm->pclk);
++}
++
++void mfpwm_release(const struct rockchip_mfpwm_func *pwmf)
++{
++ struct rockchip_mfpwm *mfpwm;
++ unsigned long flags;
++
++ if (mfpwm_check_pwmf(pwmf, "mfpwm_release"))
++ return;
++
++ mfpwm = pwmf->parent;
++
++ spin_lock_irqsave(&mfpwm->state_lock, flags);
++ mfpwm_do_release(pwmf);
++ dev_dbg(&mfpwm->pdev->dev, "%d released mfpwm, acquires now at %u\n",
++ pwmf->id, mfpwm->acquire_cnt);
++ spin_unlock_irqrestore(&mfpwm->state_lock, flags);
++}
++EXPORT_SYMBOL_NS_GPL(mfpwm_release, ROCKCHIP_MFPWM);
++
++void mfpwm_remove_func(struct rockchip_mfpwm_func *pwmf)
++{
++ struct rockchip_mfpwm *mfpwm;
++ unsigned long flags;
++
++ if (mfpwm_check_pwmf(pwmf, "mfpwm_remove_func"))
++ return;
++
++ mfpwm = pwmf->parent;
++ spin_lock_irqsave(&mfpwm->state_lock, flags);
++
++ if (mfpwm_pwmf_is_active_pwmf(pwmf)) {
++ dev_dbg(&mfpwm->pdev->dev, "removing active function %d\n",
++ pwmf->id);
++
++ while (mfpwm->acquire_cnt > 0)
++ mfpwm_do_release(pwmf);
++ for (; mfpwm->pwmclk_enable_cnt > 0; mfpwm->pwmclk_enable_cnt--)
++ clk_disable(mfpwm->chosen_clk);
++
++ mfpwm_reg_write(mfpwm->base, PWMV4_REG_ENABLE,
++ PWMV4_EN(false) | PWMV4_CLK_EN(false));
++ }
++
++ if (mfpwm->pwm_dev && mfpwm->pwm_dev->id == pwmf->id) {
++ dev_dbg(&mfpwm->pdev->dev, "clearing pwm_dev pointer\n");
++ mfpwm->pwm_dev = NULL;
++ } else if (mfpwm->counter_dev && mfpwm->counter_dev->id == pwmf->id) {
++ dev_dbg(&mfpwm->pdev->dev, "clearing counter_dev pointer\n");
++ mfpwm->counter_dev = NULL;
++ } else {
++ WARN(1, "trying to remove an unknown mfpwm device function");
++ }
++
++ spin_unlock_irqrestore(&mfpwm->state_lock, flags);
++}
++EXPORT_SYMBOL_NS_GPL(mfpwm_remove_func, ROCKCHIP_MFPWM);
++
++/**
++ * mfpwm_register_subdev - register a single mfpwm_func
++ * @mfpwm: pointer to the parent &struct rockchip_mfpwm
++ * @target: pointer to where the &struct platform_device pointer should be
++ * stored, usually a member of @mfpwm
++ * @name: sub-device name string
++ *
++ * Allocate a single &struct mfpwm_func, fill its members with appropriate data,
++ * and register a new platform device, saving its pointer to @target. The
++ * allocation is devres tracked, so will be automatically freed on mfpwm remove.
++ *
++ * Returns: 0 on success, negative errno on error
++ */
++static int mfpwm_register_subdev(struct rockchip_mfpwm *mfpwm,
++ struct platform_device **target,
++ const char *name)
++{
++ struct rockchip_mfpwm_func *func;
++ struct platform_device *child;
++
++ func = devm_kzalloc(&mfpwm->pdev->dev, sizeof(*func), GFP_KERNEL);
++ if (IS_ERR(func))
++ return PTR_ERR(func);
++ func->irq = mfpwm->irq;
++ func->parent = mfpwm;
++ func->id = atomic_inc_return(&subdev_id);
++ func->base = mfpwm->base;
++ child = platform_device_register_data(&mfpwm->pdev->dev, name, func->id,
++ func, sizeof(*func));
++
++ if (IS_ERR(child))
++ return PTR_ERR(child);
++
++ *target = child;
++
++ return 0;
++}
++
++static int mfpwm_register_subdevs(struct rockchip_mfpwm *mfpwm)
++{
++ int ret;
++
++ ret = mfpwm_register_subdev(mfpwm, &mfpwm->pwm_dev, "pwm-rockchip-v4");
++ if (ret)
++ return ret;
++
++ ret = mfpwm_register_subdev(mfpwm, &mfpwm->counter_dev,
++ "rockchip-pwm-capture");
++ if (ret)
++ goto err_unreg_pwm_dev;
++
++ return 0;
++
++err_unreg_pwm_dev:
++ platform_device_unregister(mfpwm->pwm_dev);
++
++ return ret;
++}
++
++/**
++ * mfpwm_get_clk_src - read the currently selected clock source
++ * @mfpwm: pointer to the driver's private &struct rockchip_mfpwm instance
++ *
++ * Read the device register to extract the currently selected clock source,
++ * and return it.
++ *
++ * Returns:
++ * * the numeric clock source ID on success, 0 <= id <= 2
++ * * negative errno on error
++ */
++static int mfpwm_get_clk_src(struct rockchip_mfpwm *mfpwm)
++{
++ u32 val;
++
++ clk_enable(mfpwm->pclk);
++ val = mfpwm_reg_read(mfpwm->base, PWMV4_REG_CLK_CTRL);
++ clk_disable(mfpwm->pclk);
++
++ return (val & PWMV4_CLK_SRC_MASK) >> PWMV4_CLK_SRC_SHIFT;
++}
++
++static int mfpwm_choose_clk(struct rockchip_mfpwm *mfpwm)
++{
++ int ret;
++
++ ret = mfpwm_get_clk_src(mfpwm);
++ if (ret < 0) {
++ dev_err(&mfpwm->pdev->dev, "couldn't get current clock source: %pe\n",
++ ERR_PTR(ret));
++ return ret;
++ }
++ if (ret == PWMV4_CLK_SRC_CRYSTAL) {
++ if (mfpwm->osc_clk) {
++ mfpwm->chosen_clk = mfpwm->osc_clk;
++ } else {
++ dev_warn(&mfpwm->pdev->dev, "initial state wanted 'osc' as clock source, but it's unavailable. Defaulting to 'pwm'.\n");
++ mfpwm->chosen_clk = mfpwm->pwm_clk;
++ }
++ } else {
++ mfpwm->chosen_clk = mfpwm->pwm_clk;
++ }
++
++ return clk_rate_exclusive_get(mfpwm->chosen_clk);
++}
++
++/**
++ * mfpwm_switch_clk_src - switch between PWM clock sources
++ * @mfpwm: pointer to &struct rockchip_mfpwm driver data
++ * @clk_src: one of either %PWMV4_CLK_SRC_CRYSTAL or %PWMV4_CLK_SRC_PLL
++ *
++ * Switch between clock sources, ``_exclusive_put``ing the old rate,
++ * ``clk_rate_exclusive_get``ing the new one, writing the registers and
++ * swapping out the &struct_rockchip_mfpwm->chosen_clk.
++ *
++ * Returns:
++ * * %0 - Success
++ * * %-EINVAL - A wrong @clk_src was given or it is unavailable
++ * * %-EBUSY - Device is currently in use, try again later
++ */
++__attribute__((nonnull))
++static int mfpwm_switch_clk_src(struct rockchip_mfpwm *mfpwm,
++ unsigned int clk_src)
++{
++ struct clk *prev;
++ int ret = 0;
++
++ scoped_cond_guard(spinlock_try, return -EBUSY, &mfpwm->state_lock) {
++ /* Don't fiddle with any of this stuff if the PWM is on */
++ if (mfpwm->active_func)
++ return -EBUSY;
++
++ prev = mfpwm->chosen_clk;
++ ret = mfpwm_get_clk_src(mfpwm);
++ if (ret < 0)
++ return ret;
++ if (ret == clk_src)
++ return 0;
++
++ switch (clk_src) {
++ case PWMV4_CLK_SRC_PLL:
++ mfpwm->chosen_clk = mfpwm->pwm_clk;
++ break;
++ case PWMV4_CLK_SRC_CRYSTAL:
++ if (!mfpwm->osc_clk)
++ return -EINVAL;
++ mfpwm->chosen_clk = mfpwm->osc_clk;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ clk_enable(mfpwm->pclk);
++
++ mfpwm_reg_write(mfpwm->base, PWMV4_REG_CLK_CTRL,
++ PWMV4_CLK_SRC(clk_src));
++ clk_rate_exclusive_get(mfpwm->chosen_clk);
++ if (prev)
++ clk_rate_exclusive_put(prev);
++
++ clk_disable(mfpwm->pclk);
++ }
++
++ return ret;
++}
++
++static ssize_t chosen_clock_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ struct rockchip_mfpwm *mfpwm = dev_get_drvdata(dev);
++ unsigned long clk_src = 0;
++
++ /*
++ * Why the weird indirection here? I have the suspicion that if we
++ * emitted to sysfs with the lock still held, then a nefarious program
++ * could hog the lock by somehow forcing a full buffer condition and
++ * then refusing to read from it. Don't know whether that's feasible
++ * to achieve in reality, but I don't want to find out the hard way
++ * either.
++ */
++ scoped_guard(spinlock, &mfpwm->state_lock) {
++ if (mfpwm->chosen_clk == mfpwm->pwm_clk)
++ clk_src = PWMV4_CLK_SRC_PLL;
++ else if (mfpwm->osc_clk && mfpwm->chosen_clk == mfpwm->osc_clk)
++ clk_src = PWMV4_CLK_SRC_CRYSTAL;
++ else
++ return -ENODEV;
++ }
++
++ if (clk_src == PWMV4_CLK_SRC_PLL)
++ return sysfs_emit(buf, "pll\n");
++ else if (clk_src == PWMV4_CLK_SRC_CRYSTAL)
++ return sysfs_emit(buf, "crystal\n");
++
++ return -ENODEV;
++}
++
++static ssize_t chosen_clock_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ struct rockchip_mfpwm *mfpwm = dev_get_drvdata(dev);
++ int ret;
++
++ if (sysfs_streq(buf, "pll")) {
++ ret = mfpwm_switch_clk_src(mfpwm, PWMV4_CLK_SRC_PLL);
++ if (ret)
++ return ret;
++ return count;
++ } else if (sysfs_streq(buf, "crystal")) {
++ ret = mfpwm_switch_clk_src(mfpwm, PWMV4_CLK_SRC_CRYSTAL);
++ if (ret)
++ return ret;
++ return count;
++ } else {
++ return -EINVAL;
++ }
++}
++
++static DEVICE_ATTR_RW(chosen_clock);
++
++static ssize_t available_clocks_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ struct rockchip_mfpwm *mfpwm = dev_get_drvdata(dev);
++ ssize_t size = 0;
++
++ size += sysfs_emit_at(buf, size, "pll\n");
++ if (mfpwm->osc_clk)
++ size += sysfs_emit_at(buf, size, "crystal\n");
++
++ return size;
++}
++
++static DEVICE_ATTR_RO(available_clocks);
++
++static struct attribute *mfpwm_attrs[] = {
++ &dev_attr_available_clocks.attr,
++ &dev_attr_chosen_clock.attr,
++ NULL,
++};
++
++ATTRIBUTE_GROUPS(mfpwm);
++
++static int rockchip_mfpwm_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ struct rockchip_mfpwm *mfpwm;
++ int ret = 0;
++
++ mfpwm = devm_kzalloc(&pdev->dev, sizeof(*mfpwm), GFP_KERNEL);
++ if (IS_ERR(mfpwm))
++ return PTR_ERR(mfpwm);
++
++ mfpwm->pdev = pdev;
++
++ spin_lock_init(&mfpwm->state_lock);
++
++ mfpwm->base = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(mfpwm->base))
++ return dev_err_probe(dev, PTR_ERR(mfpwm->base),
++ "failed to ioremap address\n");
++
++ mfpwm->pclk = devm_clk_get_prepared(dev, "pclk");
++ if (IS_ERR(mfpwm->pclk))
++ return dev_err_probe(dev, PTR_ERR(mfpwm->pclk),
++ "couldn't get and prepare 'pclk' clock\n");
++
++ mfpwm->irq = platform_get_irq(pdev, 0);
++ if (mfpwm->irq < 0)
++ return dev_err_probe(dev, mfpwm->irq, "couldn't get irq 0\n");
++
++ mfpwm->pwm_clk = devm_clk_get_prepared(dev, "pwm");
++ if (IS_ERR(mfpwm->pwm_clk))
++ return dev_err_probe(dev, PTR_ERR(mfpwm->pwm_clk),
++ "couldn't get and prepare 'pwm' clock\n");
++
++ mfpwm->osc_clk = devm_clk_get_optional_prepared(dev, "osc");
++ if (IS_ERR(mfpwm->osc_clk))
++ return dev_err_probe(dev, PTR_ERR(mfpwm->osc_clk),
++ "couldn't get and prepare 'osc' clock\n");
++
++ ret = mfpwm_choose_clk(mfpwm);
++ if (ret)
++ return ret;
++
++ platform_set_drvdata(pdev, mfpwm);
++
++ ret = mfpwm_register_subdevs(mfpwm);
++ if (ret) {
++ dev_err(dev, "failed to register sub-devices: %pe\n",
++ ERR_PTR(ret));
++ return ret;
++ }
++
++ return ret;
++}
++
++static void rockchip_mfpwm_remove(struct platform_device *pdev)
++{
++ struct rockchip_mfpwm *mfpwm = to_rockchip_mfpwm(pdev);
++ unsigned long flags;
++
++ spin_lock_irqsave(&mfpwm->state_lock, flags);
++
++ if (mfpwm->chosen_clk)
++ clk_rate_exclusive_put(mfpwm->chosen_clk);
++
++ spin_unlock_irqrestore(&mfpwm->state_lock, flags);
++}
++
++static const struct of_device_id rockchip_mfpwm_of_match[] = {
++ {
++ .compatible = "rockchip,rk3576-pwm",
++ },
++ { /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, rockchip_mfpwm_of_match);
++
++static struct platform_driver rockchip_mfpwm_driver = {
++ .driver = {
++ .name = KBUILD_MODNAME,
++ .of_match_table = rockchip_mfpwm_of_match,
++ .dev_groups = mfpwm_groups,
++ },
++ .probe = rockchip_mfpwm_probe,
++ .remove = rockchip_mfpwm_remove,
++};
++module_platform_driver(rockchip_mfpwm_driver);
++
++MODULE_AUTHOR("Nicolas Frattaroli ");
++MODULE_DESCRIPTION("Rockchip MFPWM Driver");
++MODULE_LICENSE("GPL");
+diff --git a/include/soc/rockchip/mfpwm.h b/include/soc/rockchip/mfpwm.h
+new file mode 100644
+index 0000000000000000000000000000000000000000..345f13f438b57159a15cb2e0ae250800fb96ed43
+--- /dev/null
++++ b/include/soc/rockchip/mfpwm.h
+@@ -0,0 +1,505 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++/*
++ * Copyright (c) 2025 Collabora Ltd.
++ *
++ * Common header file for all the Rockchip Multi-function PWM controller
++ * drivers that are spread across subsystems.
++ *
++ * Authors:
++ * Nicolas Frattaroli
++ */
++
++#ifndef __SOC_ROCKCHIP_MFPWM_H__
++#define __SOC_ROCKCHIP_MFPWM_H__
++
++#include
++#include
++#include
++#include
++
++struct rockchip_mfpwm;
++
++/**
++ * struct rockchip_mfpwm_func - struct representing a single function driver
++ *
++ * @id: unique id for this function driver instance
++ * @base: pointer to start of MMIO registers
++ * @parent: a pointer to the parent mfpwm struct
++ * @irq: the shared IRQ gotten from the parent mfpwm device
++ */
++struct rockchip_mfpwm_func {
++ int id;
++ void __iomem *base;
++ struct rockchip_mfpwm *parent;
++ int irq;
++};
++
++/*
++ * PWMV4 Register Definitions
++ * --------------------------
++ *
++ * Attributes:
++ * RW - Read-Write
++ * RO - Read-Only
++ * WO - Write-Only
++ * W1T - Write high, Self-clearing
++ * W1C - Write high to clear interrupt
++ *
++ * Bit ranges to be understood with Verilog-like semantics,
++ * e.g. [03:00] is 4 bits: 0, 1, 2 and 3.
++ *
++ * All registers must be accessed with 32-bit width accesses only
++ */
++
++#define PWMV4_REG_VERSION 0x000
++/*
++ * VERSION Register Description
++ * [31:24] RO | Hardware Major Version
++ * [23:16] RO | Hardware Minor Version docArrive
++ * [15:15] RO | Reserved
++ * [14:14] RO | Hardware supports biphasic counters
++ * [13:13] RO | Hardware supports filters
++ * [12:12] RO | Hardware supports waveform generation
++ * [11:11] RO | Hardware supports counter
++ * [10:10] RO | Hardware supports frequency metering
++ * [09:09] RO | Hardware supports power key functionality
++ * [08:08] RO | Hardware supports infrared transmissions
++ * [07:04] RO | Channel index of this instance
++ * [03:00] RO | Number of channels the base instance supports
++ */
++static inline __pure u32 pwmv4_ver_chn_num(u32 val)
++{
++ return (val & GENMASK(3, 0));
++}
++
++static inline __pure u32 pwmv4_ver_chn_idx(u32 val)
++{
++ return (val & GENMASK(7, 4)) >> 4;
++}
++
++static inline __pure u32 pwmv4_ver_major(u32 val)
++{
++ return (val & GENMASK(31, 24)) >> 24;
++}
++
++static inline __pure u32 pwmv4_ver_minor(u32 val)
++{
++ return (val & GENMASK(23, 16)) >> 16;
++}
++
++#define PWMV4_REG_ENABLE 0x004
++/*
++ * ENABLE Register Description
++ * [31:16] WO | Write Enable Mask for the lower half of the register
++ * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in
++ * the same write operation
++ * [15:06] RO | Reserved
++ * [05:05] RW | PWM Channel Counter Read Enable, 1 = enabled
++ */
++#define PWMV4_CHN_CNT_RD_EN(v) REG_UPDATE_BIT_WE((v), 5)
++/*
++ * [04:04] W1T | PWM Globally Joined Control Enable
++ * 1 = this PWM channel will be enabled by a global pwm enable
++ * bit instead of the PWM Enable bit.
++ */
++#define PWMV4_GLOBAL_CTRL_EN(v) REG_UPDATE_BIT_WE((v), 4)
++/*
++ * [03:03] RW | Force Clock Enable
++ * 0 = disabled, if the PWM channel is inactive then so is the
++ * clock prescale module
++ */
++#define PWMV4_FORCE_CLK_EN(v) REG_UPDATE_BIT_WE((v), 3)
++/*
++ * [02:02] W1T | PWM Control Update Enable
++ * 1 = enabled, commits modifications of _CTRL, _PERIOD, _DUTY and
++ * _OFFSET registers once 1 is written to it
++ */
++#define PWMV4_CTRL_UPDATE_EN(v) REG_UPDATE_BIT_WE((v), 2)
++#define PWMV4_CTRL_UPDATE_EN_MASK BIT(2)
++/*
++ * [01:01] RW | PWM Enable, 1 = enabled
++ * If in one-shot mode, clears after end of operation
++ */
++#define PWMV4_EN(v) REG_UPDATE_BIT_WE((v), 1)
++#define PWMV4_EN_MASK BIT(1)
++/*
++ * [00:00] RW | PWM Clock Enable, 1 = enabled
++ * If in one-shot mode, clears after end of operation
++ */
++#define PWMV4_CLK_EN(v) REG_UPDATE_BIT_WE((v), 0)
++#define PWMV4_CLK_EN_MASK BIT(0)
++#define PWMV4_EN_BOTH_MASK (PWMV4_EN_MASK | PWMV4_CLK_EN_MASK)
++static inline __pure bool pwmv4_is_enabled(unsigned int val)
++{
++ return (val & PWMV4_EN_BOTH_MASK);
++}
++
++#define PWMV4_REG_CLK_CTRL 0x008
++/*
++ * CLK_CTRL Register Description
++ * [31:16] WO | Write Enable Mask for the lower half of the register
++ * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in
++ * the same write operation
++ * [15:15] RW | Clock Global Selection
++ * 0 = current channel scale clock
++ * 1 = global channel scale clock
++ */
++#define PWMV4_CLK_GLOBAL(v) REG_UPDATE_BIT_WE((v), 15)
++/*
++ * [14:13] RW | Clock Source Selection
++ * 0 = Clock from PLL, frequency can be configured
++ * 1 = Clock from crystal oscillator, frequency is fixed
++ * 2 = Clock from RC oscillator, frequency is fixed
++ * 3 = Reserved
++ * NOTE: This reg is of questionable usefulness on RK3576, as it
++ * just muxes between 100m_50m_24m and 24m directly. The
++ * only use-case I can come up with is if you must use 100m
++ * or 50m PWM on one channel but absolutely require to use
++ * the lower rate 24m clock on another channel on the same
++ * chip, which doesn't seem like a realistic use case. I
++ * suspect that's why downstream doesn't use it.
++ */
++#define PWMV4_CLK_SRC_PLL 0x0U
++#define PWMV4_CLK_SRC_CRYSTAL 0x1U
++#define PWMV4_CLK_SRC_RC 0x2U
++#define PWMV4_CLK_SRC(v) REG_UPDATE_WE((v), 13, 14)
++#define PWMV4_CLK_SRC_SHIFT 13
++#define PWMV4_CLK_SRC_MASK GENMASK(14, 13)
++/*
++ * [12:04] RW | Scale Factor to apply to pre-scaled clock
++ * 1 <= v <= 256, v means clock divided by 2*v
++ */
++#define PWMV4_CLK_SCALE_F(v) REG_UPDATE_WE((v), 4, 12)
++/*
++ * [03:03] RO | Reserved
++ * [02:00] RW | Prescale Factor
++ * v here means the input clock is divided by pow(2, v)
++ */
++#define PWMV4_CLK_PRESCALE_F(v) REG_UPDATE_WE((v), 0, 2)
++
++#define PWMV4_REG_CTRL 0x00C
++/*
++ * CTRL Register Description
++ * [31:16] WO | Write Enable Mask for the lower half of the register
++ * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in
++ * the same write operation
++ * [15:09] RO | Reserved
++ * [08:06] RW | PWM Input Channel Selection
++ * By default, the channel selects its own input, but writing v
++ * here selects PWM input from channel v instead.
++ */
++#define PWMV4_CTRL_IN_SEL(v) REG_UPDATE_WE((v), 6, 8)
++/* [05:05] RW | Aligned Mode, 0 = Valid, 1 = Invalid */
++#define PWMV4_CTRL_UNALIGNED(v) REG_UPDATE_BIT_WE((v), 5)
++/* [04:04] RW | Output Mode, 0 = Left Aligned, 1 = Centre Aligned */
++#define PWMV4_LEFT_ALIGNED 0x0U
++#define PWMV4_CENTRE_ALIGNED 0x1U
++#define PWMV4_CTRL_OUT_MODE(v) REG_UPDATE_BIT_WE((v), 4)
++/*
++ * [03:03] RW | Inactive Polarity for when the channel is either disabled or
++ * has completed outputting the entire waveform in one-shot mode.
++ * 0 = Negative, 1 = Positive
++ */
++#define PWMV4_POLARITY_N 0x0U
++#define PWMV4_POLARITY_P 0x1U
++#define PWMV4_INACTIVE_POL(v) REG_UPDATE_BIT_WE((v), 3)
++/*
++ * [02:02] RW | Duty Cycle Polarity to use at the start of the waveform.
++ * 0 = Negative, 1 = Positive
++ */
++#define PWMV4_DUTY_POL(v) REG_UPDATE_BIT_WE((v), 2)
++#define PWMV4_DUTY_POL_MASK BIT(2)
++#define PWMV4_DUTY_POL_SHIFT 2
++/*
++ * [01:00] RW | PWM Mode
++ * 0 = One-shot mode, PWM generates waveform RPT times
++ * 1 = Continuous mode
++ * 2 = Capture mode, PWM measures cycles of input waveform
++ * 3 = Reserved
++ */
++#define PWMV4_MODE_ONESHOT 0x0U
++#define PWMV4_MODE_CONT 0x1U
++#define PWMV4_MODE_CAPTURE 0x2U
++#define PWMV4_MODE(v) REG_UPDATE_WE((v), 0, 1)
++#define PWMV4_CTRL_COM_FLAGS (PWMV4_INACTIVE_POL(PWMV4_POLARITY_N) | \
++ PWMV4_DUTY_POL(PWMV4_POLARITY_P) | \
++ PWMV4_CTRL_OUT_MODE(PWMV4_LEFT_ALIGNED) | \
++ PWMV4_CTRL_UNALIGNED(true))
++#define PWMV4_CTRL_CONT_FLAGS (PWMV4_MODE(PWMV4_MODE_CONT) | \
++ PWMV4_CTRL_COM_FLAGS)
++#define PWMV4_CTRL_CAP_FLAGS (PWMV4_MODE(PWMV4_MODE_CAPTURE) | \
++ PWMV4_CTRL_COM_FLAGS)
++
++#define PWMV4_REG_PERIOD 0x010
++/*
++ * PERIOD Register Description
++ * [31:00] RW | Period of the output waveform
++ * Constraints: should be even if CTRL_OUT_MODE is CENTRE_ALIGNED
++ */
++
++#define PWMV4_REG_DUTY 0x014
++/*
++ * DUTY Register Description
++ * [31:00] RW | Duty cycle of the output waveform
++ * Constraints: should be even if CTRL_OUT_MODE is CENTRE_ALIGNED
++ */
++
++#define PWMV4_REG_OFFSET 0x018
++/*
++ * OFFSET Register Description
++ * [31:00] RW | Offset of the output waveform, based on the PWM clock
++ * Constraints: 0 <= v <= (PERIOD - DUTY)
++ */
++
++#define PWMV4_REG_RPT 0x01C
++/*
++ * RPT Register Description
++ * [31:16] RW | Second dimensional of the effective number of waveform
++ * repetitions. Increases by one every first dimensional times.
++ * Value `n` means `n + 1` repetitions. The final number of
++ * repetitions of the waveform in one-shot mode is:
++ * `(first_dimensional + 1) * (second_dimensional + 1)`
++ * [15:00] RW | First dimensional of the effective number of waveform
++ * repetitions. Value `n` means `n + 1` repetitions.
++ */
++
++#define PWMV4_REG_FILTER_CTRL 0x020
++/*
++ * FILTER_CTRL Register Description
++ * [31:16] WO | Write Enable Mask for the lower half of the register
++ * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in
++ * the same write operation
++ * [15:10] RO | Reserved
++ * [09:04] RW | Filter window number
++ * [03:01] RO | Reserved
++ * [00:00] RW | Filter Enable, 0 = disabled, 1 = enabled
++ */
++
++#define PWMV4_REG_CNT 0x024
++/*
++ * CNT Register Description
++ * [31:00] RO | Current value of the PWM Channel 0 counter in pwm clock cycles,
++ * 0 <= v <= 2^32-1
++ */
++
++#define PWMV4_REG_ENABLE_DELAY 0x028
++/*
++ * ENABLE_DELAY Register Description
++ * [31:16] RO | Reserved
++ * [15:00] RW | PWM enable delay, in an unknown unit but probably cycles
++ */
++
++#define PWMV4_REG_HPC 0x02C
++/*
++ * HPC Register Description
++ * [31:00] RW | Number of effective high polarity cycles of the input waveform
++ * in capture mode. Based on the PWM clock. 0 <= v <= 2^32-1
++ */
++
++#define PWMV4_REG_LPC 0x030
++/*
++ * LPC Register Description
++ * [31:00] RW | Number of effective low polarity cycles of the input waveform
++ * in capture mode. Based on the PWM clock. 0 <= v <= 2^32-1
++ */
++
++#define PWMV4_REG_BIPHASIC_CNT_CTRL0 0x040
++/*
++ * BIPHASIC_CNT_CTRL0 Register Description
++ * [31:16] WO | Write Enable Mask for the lower half of the register
++ * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in
++ * the same write operation
++ * [15:10] RO | Reserved
++ * [09:09] RW | Biphasic Counter Phase Edge Selection for mode 0,
++ * 0 = rising edge (posedge), 1 = falling edge (negedge)
++ * [08:08] RW | Biphasic Counter Clock force enable, 1 = force enable
++ * [07:07] W1T | Synchronous Enable
++ * [06:06] W1T | Mode Switch
++ * 0 = Normal Mode, 1 = Switch timer clock and measured clock
++ * Constraints: "Biphasic Counter Mode" must be 0 if this is 1
++ * [05:03] RW | Biphasic Counter Mode
++ * 0x0 = Mode 0, 0x1 = Mode 1, 0x2 = Mode 2, 0x3 = Mode 3,
++ * 0x4 = Mode 4, 0x5 = Reserved
++ * [02:02] RW | Biphasic Counter Clock Selection
++ * 0 = clock is from PLL and frequency can be configured
++ * 1 = clock is from crystal oscillator and frequency is fixed
++ * [01:01] RW | Biphasic Counter Continuous Mode
++ * [00:00] W1T | Biphasic Counter Enable
++ */
++
++#define PWMV4_REG_BIPHASIC_CNT_CTRL1 0x044
++/*
++ * BIPHASIC_CNT_CTRL1 Register Description
++ * [31:16] WO | Write Enable Mask for the lower half of the register
++ * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in
++ * the same write operation
++ * [15:11] RO | Reserved
++ * [10:04] RW | Biphasic Counter Filter Window Number
++ * [03:01] RO | Reserved
++ * [00:00] RW | Biphasic Counter Filter Enable
++ */
++
++#define PWMV4_REG_BIPHASIC_CNT_TIMER 0x048
++/*
++ * BIPHASIC_CNT_TIMER Register Description
++ * [31:00] RW | Biphasic Counter Timer Value, in number of biphasic counter
++ * timer clock cycles
++ */
++
++#define PWMV4_REG_BIPHASIC_CNT_RES 0x04C
++/*
++ * BIPHASIC_CNT_RES Register Description
++ * [31:00] RO | Biphasic Counter Result Value
++ * Constraints: Can only be read after INTSTS[9] is asserted
++ */
++
++#define PWMV4_REG_BIPHASIC_CNT_RES_S 0x050
++/*
++ * BIPHASIC_CNT_RES_S Register Description
++ * [31:00] RO | Biphasic Counter Result Value with synchronised processing
++ * Can be read in real-time if BIPHASIC_CNT_CTRL0[7] was set to 1
++ */
++
++#define PWMV4_REG_INTSTS 0x070
++/*
++ * INTSTS Register Description
++ * [31:10] RO | Reserved
++ * [09:09] W1C | Biphasic Counter Interrupt Status, 1 = interrupt asserted
++ * [08:08] W1C | Waveform Middle Interrupt Status, 1 = interrupt asserted
++ * [07:07] W1C | Waveform Max Interrupt Status, 1 = interrupt asserted
++ * [06:06] W1C | IR Transmission End Interrupt Status, 1 = interrupt asserted
++ * [05:05] W1C | Power Key Match Interrupt Status, 1 = interrupt asserted
++ * [04:04] W1C | Frequency Meter Interrupt Status, 1 = interrupt asserted
++ * [03:03] W1C | Reload Interrupt Status, 1 = interrupt asserted
++ * [02:02] W1C | Oneshot End Interrupt Status, 1 = interrupt asserted
++ * [01:01] W1C | HPC Capture Interrupt Status, 1 = interrupt asserted
++ * [00:00] W1C | LPC Capture Interrupt Status, 1 = interrupt asserted
++ */
++#define PWMV4_INT_LPC BIT(0)
++#define PWMV4_INT_HPC BIT(1)
++#define PWMV4_INT_LPC_W(v) REG_UPDATE_BIT_WE(((v) ? 1 : 0), 0)
++#define PWMV4_INT_HPC_W(v) REG_UPDATE_BIT_WE(((v) ? 1 : 0), 1)
++
++#define PWMV4_REG_INT_EN 0x074
++/*
++ * INT_EN Register Description
++ * [31:16] WO | Write Enable Mask for the lower half of the register
++ * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in
++ * the same write operation
++ * [15:10] RO | Reserved
++ * [09:09] RW | Biphasic Counter Interrupt Enable, 1 = enabled
++ * [08:08] W1C | Waveform Middle Interrupt Enable, 1 = enabled
++ * [07:07] W1C | Waveform Max Interrupt Enable, 1 = enabled
++ * [06:06] W1C | IR Transmission End Interrupt Enable, 1 = enabled
++ * [05:05] W1C | Power Key Match Interrupt Enable, 1 = enabled
++ * [04:04] W1C | Frequency Meter Interrupt Enable, 1 = enabled
++ * [03:03] W1C | Reload Interrupt Enable, 1 = enabled
++ * [02:02] W1C | Oneshot End Interrupt Enable, 1 = enabled
++ * [01:01] W1C | HPC Capture Interrupt Enable, 1 = enabled
++ * [00:00] W1C | LPC Capture Interrupt Enable, 1 = enabled
++ */
++
++#define PWMV4_REG_INT_MASK 0x078
++/*
++ * INT_MASK Register Description
++ * [31:16] WO | Write Enable Mask for the lower half of the register
++ * Set bit `n` here to 1 if you wish to modify bit `n >> 16` in
++ * the same write operation
++ * [15:10] RO | Reserved
++ * [09:09] RW | Biphasic Counter Interrupt Masked, 1 = masked
++ * [08:08] W1C | Waveform Middle Interrupt Masked, 1 = masked
++ * [07:07] W1C | Waveform Max Interrupt Masked, 1 = masked
++ * [06:06] W1C | IR Transmission End Interrupt Masked, 1 = masked
++ * [05:05] W1C | Power Key Match Interrupt Masked, 1 = masked
++ * [04:04] W1C | Frequency Meter Interrupt Masked, 1 = masked
++ * [03:03] W1C | Reload Interrupt Masked, 1 = masked
++ * [02:02] W1C | Oneshot End Interrupt Masked, 1 = masked
++ * [01:01] W1C | HPC Capture Interrupt Masked, 1 = masked
++ * [00:00] W1C | LPC Capture Interrupt Masked, 1 = masked
++ */
++
++static inline u32 mfpwm_reg_read(void __iomem *base, u32 reg)
++{
++ return readl(base + reg);
++}
++
++static inline void mfpwm_reg_write(void __iomem *base, u32 reg, u32 val)
++{
++ writel(val, base + reg);
++}
++
++/**
++ * mfpwm_clk_get_rate - get the currently used clock's rate, in Hz
++ * @mfpwm: pointer to the &struct rockchip_mfpwm instance
++ *
++ * Returns: %0 on error, clock rate in Hz on success
++ */
++unsigned long mfpwm_clk_get_rate(struct rockchip_mfpwm *mfpwm);
++
++/**
++ * mfpwm_acquire - try becoming the active mfpwm function device
++ * @pwmf: pointer to the calling driver instance's &struct rockchip_mfpwm_func
++ *
++ * mfpwm device "function" drivers must call this function before doing anything
++ * that either modifies or relies on the parent device's state, such as clocks,
++ * enabling/disabling outputs, modifying shared regs etc.
++ *
++ * The return statues should always be checked.
++ *
++ * All mfpwm_acquire() calls must be balanced with corresponding mfpwm_release()
++ * calls once the device is no longer making changes that affect other devices,
++ * or stops producing user-visible effects that depend on the current device
++ * state being kept as-is. (e.g. after the PWM output signal is stopped)
++ *
++ * The same device function may mfpwm_acquire() multiple times while it already
++ * is active, i.e. it is re-entrant, though it needs to balance this with the
++ * same number of mfpwm_release() calls.
++ *
++ * Context: This function does not sleep.
++ *
++ * Return:
++ * * %0 - success
++ * * %-EBUSY - a different device function is active
++ * * %-EOVERFLOW - the acquire counter is at its maximum
++ */
++int __must_check mfpwm_acquire(struct rockchip_mfpwm_func *pwmf);
++
++/**
++ * mfpwm_release - drop usage of active mfpwm device function by 1
++ * @pwmf: pointer to the calling driver instance's &struct rockchip_mfpwm_func
++ *
++ * This is the balancing call to mfpwm_acquire(). If no users of the device
++ * function remain, set the mfpwm device to have no active device function,
++ * allowing other device functions to claim it.
++ */
++void mfpwm_release(const struct rockchip_mfpwm_func *pwmf);
++
++/**
++ * mfpwm_pwmclk_enable - enable the pwm clock the signal and timing is based on
++ * @pwmf: pointer to the calling driver instance's &struct rockchip_mfpwm_func
++ *
++ * Context: must be the active device function to call this
++ *
++ * Returns: 0 on success, negative errno on error.
++ */
++int mfpwm_pwmclk_enable(struct rockchip_mfpwm_func *pwmf);
++
++/**
++ * mfpwm_pwmclk_disable - disable the pwm clock the signal and timing is based on
++ * @pwmf: pointer to the calling driver instance's &struct rockchip_mfpwm_func
++ *
++ * Context: must be the active device function to call this
++ */
++void mfpwm_pwmclk_disable(struct rockchip_mfpwm_func *pwmf);
++
++/**
++ * mfpwm_remove_func - remove a device function driver from the mfpwm
++ * @pwmf: pointer to the calling driver instance's &struct rockchip_mfpwm_func
++ *
++ * If the device function driver described by @pwmf is the currently active
++ * device function, then it'll have its mfpwm_acquires and its pwmclk_enables
++ * balanced and be removed as the active device function driver.
++ */
++void mfpwm_remove_func(struct rockchip_mfpwm_func *pwmf);
++
++#endif /* __SOC_ROCKCHIP_MFPWM_H__ */
+
+--
+2.49.0
diff --git a/lede/target/linux/rockchip/patches-6.12/341-05-pwm-add-rockchip-pwmv4-driver.patch b/lede/target/linux/rockchip/patches-6.12/341-05-pwm-add-rockchip-pwmv4-driver.patch
new file mode 100644
index 0000000000..63c7e03c28
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/341-05-pwm-add-rockchip-pwmv4-driver.patch
@@ -0,0 +1,401 @@
+Signed-off-by: Nicolas Frattaroli
+---
+ MAINTAINERS | 1 +
+ drivers/pwm/Kconfig | 13 ++
+ drivers/pwm/Makefile | 1 +
+ drivers/pwm/pwm-rockchip-v4.c | 336 ++++++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 351 insertions(+)
+
+diff --git a/MAINTAINERS b/MAINTAINERS
+index e6a9347be1e7889089e1d9e655cb23c2d8399b40..3ddd245fd4ad8d9ed2e762910a7a1f6436f93e34 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -20891,6 +20891,7 @@ L: linux-rockchip@lists.infradead.org
+ L: linux-pwm@vger.kernel.org
+ S: Maintained
+ F: Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml
++F: drivers/pwm/pwm-rockchip-v4.c
+ F: drivers/soc/rockchip/mfpwm.c
+ F: include/soc/rockchip/mfpwm.h
+
+diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
+index 4731d5b90d7edcc61138e4a5bf7e98906953ece4..242039f62ab091cea337bf27ef310bcf696b6ed0 100644
+--- a/drivers/pwm/Kconfig
++++ b/drivers/pwm/Kconfig
+@@ -540,6 +540,19 @@ config PWM_ROCKCHIP
+ Generic PWM framework driver for the PWM controller found on
+ Rockchip SoCs.
+
++config PWM_ROCKCHIP_V4
++ tristate "Rockchip PWM v4 support"
++ depends on ARCH_ROCKCHIP || COMPILE_TEST
++ depends on ROCKCHIP_MFPWM
++ depends on HAS_IOMEM
++ help
++ Generic PWM framework driver for the PWM controller found on
++ later Rockchip SoCs such as the RK3576.
++
++ Uses the Rockchip Multi-function PWM controller driver infrastructure
++ to guarantee fearlessly concurrent operation with other functions of
++ the same device implemented by drivers in other subsystems.
++
+ config PWM_RZ_MTU3
+ tristate "Renesas RZ/G2L MTU3a PWM Timer support"
+ depends on RZ_MTU3
+diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
+index 539e0def3f82fcb866ab83a0346a15f7efdd7127..b5aca7ff58ac83f844581df526624617025291de 100644
+--- a/drivers/pwm/Makefile
++++ b/drivers/pwm/Makefile
+@@ -49,6 +49,7 @@ obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o
+ obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o
+ obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o
+ obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
++obj-$(CONFIG_PWM_ROCKCHIP_V4) += pwm-rockchip-v4.o
+ obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o
+ obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
+ obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
+diff --git a/drivers/pwm/pwm-rockchip-v4.c b/drivers/pwm/pwm-rockchip-v4.c
+new file mode 100644
+index 0000000000000000000000000000000000000000..980b27454ef9b930bef0496ca528533cf419fa0e
+--- /dev/null
++++ b/drivers/pwm/pwm-rockchip-v4.c
+@@ -0,0 +1,336 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (c) 2025 Collabora Ltd.
++ *
++ * A Pulse-Width-Modulation (PWM) generator driver for the generators found in
++ * Rockchip SoCs such as the RK3576, internally referred to as "PWM v4". Uses
++ * the MFPWM infrastructure to guarantee exclusive use over the device without
++ * other functions of the device from different drivers interfering with its
++ * operation while it's active.
++ *
++ * Authors:
++ * Nicolas Frattaroli
++ */
++
++#include
++#include
++#include
++
++struct rockchip_pwm_v4 {
++ struct rockchip_mfpwm_func *pwmf;
++ struct pwm_chip chip;
++};
++
++struct rockchip_pwm_v4_wf {
++ u32 period;
++ u32 duty;
++ u32 offset;
++ u8 enable;
++};
++
++static inline struct rockchip_pwm_v4 *to_rockchip_pwm_v4(struct pwm_chip *chip)
++{
++ return pwmchip_get_drvdata(chip);
++}
++
++/**
++ * rockchip_pwm_v4_round_single - convert a PWM parameter to hardware
++ * @rate: clock rate of the PWM clock, as per clk_get_rate
++ * @in_val: parameter in nanoseconds to convert
++ * @out_val: pointer to location where converted result should be stored.
++ *
++ * If @out_val is %NULL, no calculation is performed.
++ *
++ * Return:
++ * * %0 - Success
++ * * %-EOVERFLOW - Result too large for target type
++ */
++static int rockchip_pwm_v4_round_single(unsigned long rate, u64 in_val,
++ u32 *out_val)
++{
++ u64 tmp;
++
++ if (!out_val)
++ return 0;
++
++ tmp = mult_frac(rate, in_val, NSEC_PER_SEC);
++ if (tmp > U32_MAX)
++ return -EOVERFLOW;
++
++ *out_val = tmp;
++
++ return 0;
++}
++
++/**
++ * rockchip_pwm_v4_round_params - convert PWM parameters to hardware
++ * @rate: PWM clock rate to do the calculations at
++ * @duty: PWM duty cycle in nanoseconds
++ * @period: PWM period in nanoseconds
++ * @offset: PWM offset in nanoseconds
++ * @out_duty: pointer to where the rounded duty value should be stored
++ * if NULL, don't calculate or store it
++ * @out_period: pointer to where the rounded period value should be stored
++ * if NULL, don't calculate or store it
++ * @out_offset: pointer to where the rounded offset value should be stored
++ * if NULL, don't calculate or store it
++ *
++ * Convert nanosecond-based duty/period/offset parameters to the PWM hardware's
++ * native rounded representation in number of cycles at clock rate @rate. If an
++ * out_ parameter is a NULL pointer, the corresponding parameter will not be
++ * calculated or stored. Should an overflow error occur for any of the
++ * parameters, assume the data at all the out_ locations is invalid and may not
++ * even have been touched at all.
++ *
++ * Return:
++ * * %0 - Success
++ * * %-EOVERFLOW - One of the results is too large for the PWM hardware
++ */
++static int rockchip_pwm_v4_round_params(unsigned long rate, u64 duty,
++ u64 period, u64 offset, u32 *out_duty,
++ u32 *out_period, u32 *out_offset)
++{
++ int ret;
++
++ ret = rockchip_pwm_v4_round_single(rate, duty, out_duty);
++ if (ret)
++ return ret;
++
++ ret = rockchip_pwm_v4_round_single(rate, period, out_period);
++ if (ret)
++ return ret;
++
++ ret = rockchip_pwm_v4_round_single(rate, offset, out_offset);
++ if (ret)
++ return ret;
++
++ return 0;
++}
++
++static int rockchip_pwm_v4_round_wf_tohw(struct pwm_chip *chip,
++ struct pwm_device *pwm,
++ const struct pwm_waveform *wf,
++ void *_wfhw)
++{
++ struct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip);
++ struct rockchip_pwm_v4_wf *wfhw = _wfhw;
++ unsigned long rate;
++ int ret = 0;
++
++ /* We do not want chosen_clk to change out from under us here */
++ ret = mfpwm_acquire(pc->pwmf);
++ if (ret)
++ return ret;
++
++ rate = mfpwm_clk_get_rate(pc->pwmf->parent);
++
++ ret = rockchip_pwm_v4_round_params(rate, wf->duty_length_ns,
++ wf->period_length_ns,
++ wf->duty_offset_ns, &wfhw->duty,
++ &wfhw->period, &wfhw->offset);
++
++ if (wf->period_length_ns > 0)
++ wfhw->enable = PWMV4_EN_BOTH_MASK;
++ else
++ wfhw->enable = 0;
++
++ dev_dbg(&chip->dev, "tohw: duty = %u, period = %u, offset = %u, rate %lu\n",
++ wfhw->duty, wfhw->period, wfhw->offset, rate);
++
++ mfpwm_release(pc->pwmf);
++ return ret;
++}
++
++static int rockchip_pwm_v4_round_wf_fromhw(struct pwm_chip *chip,
++ struct pwm_device *pwm,
++ const void *_wfhw,
++ struct pwm_waveform *wf)
++{
++ struct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip);
++ const struct rockchip_pwm_v4_wf *wfhw = _wfhw;
++ unsigned long rate;
++ int ret = 0;
++
++ /* We do not want chosen_clk to change out from under us here */
++ ret = mfpwm_acquire(pc->pwmf);
++ if (ret)
++ return ret;
++
++ rate = mfpwm_clk_get_rate(pc->pwmf->parent);
++
++ /* Let's avoid a cool division-by-zero if the clock's busted. */
++ if (!rate) {
++ ret = -EINVAL;
++ goto out_mfpwm_release;
++ }
++
++ wf->duty_length_ns = mult_frac(wfhw->duty, NSEC_PER_SEC, rate);
++
++ if (pwmv4_is_enabled(wfhw->enable))
++ wf->period_length_ns = mult_frac(wfhw->period, NSEC_PER_SEC,
++ rate);
++ else
++ wf->period_length_ns = 0;
++
++ wf->duty_offset_ns = mult_frac(wfhw->offset, NSEC_PER_SEC, rate);
++
++ dev_dbg(&chip->dev, "fromhw: duty = %llu, period = %llu, offset = %llu\n",
++ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns);
++
++out_mfpwm_release:
++ mfpwm_release(pc->pwmf);
++ return ret;
++}
++
++static int rockchip_pwm_v4_read_wf(struct pwm_chip *chip, struct pwm_device *pwm,
++ void *_wfhw)
++{
++ struct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip);
++ struct rockchip_pwm_v4_wf *wfhw = _wfhw;
++ int ret = 0;
++
++
++ ret = mfpwm_acquire(pc->pwmf);
++ if (ret)
++ return ret;
++
++ wfhw->period = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_PERIOD);
++ wfhw->duty = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_DUTY);
++ wfhw->offset = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_OFFSET);
++ wfhw->enable = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_ENABLE) & PWMV4_EN_BOTH_MASK;
++
++ mfpwm_release(pc->pwmf);
++
++ return 0;
++}
++
++static int rockchip_pwm_v4_write_wf(struct pwm_chip *chip, struct pwm_device *pwm,
++ const void *_wfhw)
++{
++ struct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip);
++ const struct rockchip_pwm_v4_wf *wfhw = _wfhw;
++ bool was_enabled = false;
++ int ret = 0;
++
++ ret = mfpwm_acquire(pc->pwmf);
++ if (ret)
++ return ret;
++
++ was_enabled = pwmv4_is_enabled(mfpwm_reg_read(pc->pwmf->base,
++ PWMV4_REG_ENABLE));
++
++ /*
++ * "But Nicolas", you ask with valid concerns, "why would you enable the
++ * PWM before setting all the parameter registers?"
++ *
++ * Excellent question, Mr. Reader M. Strawman! The RK3576 TRM Part 1
++ * Section 34.6.3 specifies that this is the intended order of writes.
++ * Doing the PWM_EN and PWM_CLK_EN writes after the params but before
++ * the CTRL_UPDATE_EN, or even after the CTRL_UPDATE_EN, results in
++ * erratic behaviour where repeated turning on and off of the PWM may
++ * not turn it off under all circumstances. This is also why we don't
++ * use relaxed writes; it's not worth the footgun.
++ */
++ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE,
++ REG_UPDATE_WE(wfhw->enable, 0, 1));
++
++ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_PERIOD, wfhw->period);
++ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_DUTY, wfhw->duty);
++ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_OFFSET, wfhw->offset);
++
++ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_CTRL, PWMV4_CTRL_CONT_FLAGS);
++
++ /* Commit new configuration to hardware output. */
++ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE,
++ PWMV4_CTRL_UPDATE_EN(1));
++
++ if (pwmv4_is_enabled(wfhw->enable)) {
++ if (!was_enabled) {
++ dev_dbg(&chip->dev, "enabling PWM output\n");
++ ret = mfpwm_pwmclk_enable(pc->pwmf);
++ if (ret)
++ goto err_mfpwm_release;
++
++ /*
++ * Output should be on now, acquire device to guarantee
++ * exclusion with other device functions while it's on.
++ */
++ ret = mfpwm_acquire(pc->pwmf);
++ if (ret)
++ goto err_mfpwm_release;
++ }
++ } else if (was_enabled) {
++ dev_dbg(&chip->dev, "disabling PWM output\n");
++ mfpwm_pwmclk_disable(pc->pwmf);
++ /* Output is off now, extra release to balance extra acquire */
++ mfpwm_release(pc->pwmf);
++ }
++
++err_mfpwm_release:
++ mfpwm_release(pc->pwmf);
++
++ return ret;
++}
++
++/* We state the PWM chip is atomic, so none of these functions should sleep. */
++static const struct pwm_ops rockchip_pwm_v4_ops = {
++ .sizeof_wfhw = sizeof(struct rockchip_pwm_v4_wf),
++ .round_waveform_tohw = rockchip_pwm_v4_round_wf_tohw,
++ .round_waveform_fromhw = rockchip_pwm_v4_round_wf_fromhw,
++ .read_waveform = rockchip_pwm_v4_read_wf,
++ .write_waveform = rockchip_pwm_v4_write_wf,
++};
++
++static int rockchip_pwm_v4_probe(struct platform_device *pdev)
++{
++ struct rockchip_mfpwm_func *pwmf = dev_get_platdata(&pdev->dev);
++ struct rockchip_pwm_v4 *pc;
++ struct pwm_chip *chip;
++ int ret;
++
++ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*pc));
++ if (IS_ERR(chip))
++ return PTR_ERR(chip);
++
++ pc = to_rockchip_pwm_v4(chip);
++ pc->pwmf = pwmf;
++
++ platform_set_drvdata(pdev, pc);
++
++ chip->ops = &rockchip_pwm_v4_ops;
++ chip->atomic = true;
++
++ ret = pwmchip_add(chip);
++ if (ret)
++ return dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n");
++
++ return 0;
++}
++
++static void rockchip_pwm_v4_remove(struct platform_device *pdev)
++{
++ struct rockchip_pwm_v4 *pc = platform_get_drvdata(pdev);
++
++ mfpwm_remove_func(pc->pwmf);
++}
++
++static const struct platform_device_id rockchip_pwm_v4_ids[] = {
++ { .name = "pwm-rockchip-v4", },
++ { /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(platform, rockchip_pwm_v4_ids);
++
++static struct platform_driver rockchip_pwm_v4_driver = {
++ .probe = rockchip_pwm_v4_probe,
++ .remove = rockchip_pwm_v4_remove,
++ .driver = {
++ .name = "pwm-rockchip-v4",
++ },
++ .id_table = rockchip_pwm_v4_ids,
++};
++module_platform_driver(rockchip_pwm_v4_driver);
++
++MODULE_AUTHOR("Nicolas Frattaroli ");
++MODULE_DESCRIPTION("Rockchip PWMv4 Driver");
++MODULE_LICENSE("GPL");
++MODULE_IMPORT_NS("ROCKCHIP_MFPWM");
+
+--
+2.49.0
diff --git a/lede/target/linux/rockchip/patches-6.12/341-06-counter-add-rockchip-pwm-capture-driver.patch b/lede/target/linux/rockchip/patches-6.12/341-06-counter-add-rockchip-pwm-capture-driver.patch
new file mode 100644
index 0000000000..af8f0a065b
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/341-06-counter-add-rockchip-pwm-capture-driver.patch
@@ -0,0 +1,403 @@
+Signed-off-by: Nicolas Frattaroli
+---
+ MAINTAINERS | 1 +
+ drivers/counter/Kconfig | 13 ++
+ drivers/counter/Makefile | 1 +
+ drivers/counter/rockchip-pwm-capture.c | 341 +++++++++++++++++++++++++++++++++
+ 4 files changed, 356 insertions(+)
+
+diff --git a/MAINTAINERS b/MAINTAINERS
+index 3ddd245fd4ad8d9ed2e762910a7a1f6436f93e34..e5d26256d05a04a9642371cf3dbb4dd0c1c34e68 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -20891,6 +20891,7 @@ L: linux-rockchip@lists.infradead.org
+ L: linux-pwm@vger.kernel.org
+ S: Maintained
+ F: Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml
++F: drivers/counter/rockchip-pwm-capture.c
+ F: drivers/pwm/pwm-rockchip-v4.c
+ F: drivers/soc/rockchip/mfpwm.c
+ F: include/soc/rockchip/mfpwm.h
+diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig
+index d30d22dfe57741b145a45632b6325d5f9680590e..01b4f5c326478c73b518041830ee0d65b37f6833 100644
+--- a/drivers/counter/Kconfig
++++ b/drivers/counter/Kconfig
+@@ -90,6 +90,19 @@ config MICROCHIP_TCB_CAPTURE
+ To compile this driver as a module, choose M here: the
+ module will be called microchip-tcb-capture.
+
++config ROCKCHIP_PWM_CAPTURE
++ tristate "Rockchip PWM Counter Capture driver"
++ depends on ARCH_ROCKCHIP || COMPILE_TEST
++ depends on ROCKCHIP_MFPWM
++ depends on HAS_IOMEM
++ help
++ Generic counter framework driver for the multi-function PWM on
++ Rockchip SoCs such as the RK3576.
++
++ Uses the Rockchip Multi-function PWM controller driver infrastructure
++ to guarantee exclusive operation with other functions of the same
++ device implemented by drivers in other subsystems.
++
+ config RZ_MTU3_CNT
+ tristate "Renesas RZ/G2L MTU3a counter driver"
+ depends on RZ_MTU3
+diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile
+index fa3c1d08f7068835aa912aa13bc92bcfd44d16fb..2bfcfc2c584bd174a9885064746a98f15b204aec 100644
+--- a/drivers/counter/Makefile
++++ b/drivers/counter/Makefile
+@@ -17,3 +17,4 @@ obj-$(CONFIG_FTM_QUADDEC) += ftm-quaddec.o
+ obj-$(CONFIG_MICROCHIP_TCB_CAPTURE) += microchip-tcb-capture.o
+ obj-$(CONFIG_INTEL_QEP) += intel-qep.o
+ obj-$(CONFIG_TI_ECAP_CAPTURE) += ti-ecap-capture.o
++obj-$(CONFIG_ROCKCHIP_PWM_CAPTURE) += rockchip-pwm-capture.o
+diff --git a/drivers/counter/rockchip-pwm-capture.c b/drivers/counter/rockchip-pwm-capture.c
+new file mode 100644
+index 0000000000000000000000000000000000000000..b2bfa2c6e04dfa0410fa0d7ef1c395217e4a9db2
+--- /dev/null
++++ b/drivers/counter/rockchip-pwm-capture.c
+@@ -0,0 +1,341 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (c) 2025 Collabora Ltd.
++ *
++ * A counter driver for the Pulse-Width-Modulation (PWM) hardware found on
++ * Rockchip SoCs such as the RK3576, internally referred to as "PWM v4". It
++ * allows for measuring the period and duty cycle in nanoseconds through the
++ * generic counter framework, while guaranteeing exclusive use over the MFPWM
++ * device while the counter is enabled.
++ *
++ * Authors:
++ * Nicolas Frattaroli
++ */
++
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++
++#define RKPWMC_INT_MASK (PWMV4_INT_LPC | PWMV4_INT_HPC)
++/*
++ * amount of jiffies between no PWM signal change and us deciding PWM is off.
++ * PWM signals with a period longer than this won't be detected by us, which is
++ * the trade-off we make to have a faster response time when a signal is turned
++ * off.
++ */
++#define RKPWMC_CLEAR_DELAY (max(msecs_to_jiffies(100), 1))
++
++struct rockchip_pwm_capture {
++ struct rockchip_mfpwm_func *pwmf;
++ bool is_enabled;
++ spinlock_t enable_lock;
++ atomic_t lpc;
++ atomic_t hpc;
++ atomic_t captures_left;
++ struct delayed_work clear_capture;
++};
++
++static struct counter_signal rkpwmc_signals[] = {
++ {
++ .id = 0,
++ .name = "Channel 1"
++ },
++};
++
++static const enum counter_synapse_action rkpwmc_hpc_lpc_actions[] = {
++ COUNTER_SYNAPSE_ACTION_BOTH_EDGES,
++
++};
++
++static struct counter_synapse rkpwmc_pwm_synapses[] = {
++ {
++ .actions_list = rkpwmc_hpc_lpc_actions,
++ .num_actions = ARRAY_SIZE(rkpwmc_hpc_lpc_actions),
++ .signal = &rkpwmc_signals[0]
++ },
++};
++
++static const enum counter_function rkpwmc_functions[] = {
++ COUNTER_FUNCTION_INCREASE,
++};
++
++static int rkpwmc_enable_read(struct counter_device *counter,
++ struct counter_count *count,
++ u8 *enable)
++{
++ struct rockchip_pwm_capture *pc = counter_priv(counter);
++
++ guard(spinlock)(&pc->enable_lock);
++
++ *enable = pc->is_enabled;
++
++ return 0;
++}
++
++static int rkpwmc_enable_write(struct counter_device *counter,
++ struct counter_count *count,
++ u8 enable)
++{
++ struct rockchip_pwm_capture *pc = counter_priv(counter);
++ int ret;
++
++ guard(spinlock)(&pc->enable_lock);
++
++ if (!!enable != pc->is_enabled) {
++ ret = mfpwm_acquire(pc->pwmf);
++ if (ret)
++ return ret;
++
++ if (enable) {
++ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE,
++ PWMV4_EN(false));
++ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_CTRL,
++ PWMV4_CTRL_CAP_FLAGS);
++ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INT_EN,
++ PWMV4_INT_LPC_W(true) |
++ PWMV4_INT_HPC_W(true));
++ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE,
++ PWMV4_EN(true) | PWMV4_CLK_EN(true));
++
++ ret = mfpwm_pwmclk_enable(pc->pwmf);
++ if (ret)
++ goto err_release;
++
++ ret = mfpwm_acquire(pc->pwmf);
++ if (ret)
++ goto err_disable_pwm_clk;
++
++ atomic_set(&pc->captures_left, 4);
++ pc->is_enabled = true;
++ } else {
++ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INT_EN,
++ PWMV4_INT_LPC_W(false) |
++ PWMV4_INT_HPC_W(false));
++ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE,
++ PWMV4_EN(false) | PWMV4_CLK_EN(false));
++ /*
++ * Do not use cancel_delayed_work here unless you want
++ * to cause the interrupt handler, which may still be
++ * running at this point, to stall. Similarly, don't do
++ * flush_delayed_work since it may sleep.
++ */
++ mod_delayed_work(system_bh_wq, &pc->clear_capture, 0);
++ mfpwm_pwmclk_disable(pc->pwmf);
++ pc->is_enabled = false;
++ mfpwm_release(pc->pwmf);
++ }
++
++ mfpwm_release(pc->pwmf);
++ }
++
++ return 0;
++
++err_disable_pwm_clk:
++ mfpwm_pwmclk_disable(pc->pwmf);
++err_release:
++ mfpwm_release(pc->pwmf);
++
++ return ret;
++}
++
++static struct counter_comp rkpwmc_ext[] = {
++ COUNTER_COMP_ENABLE(rkpwmc_enable_read, rkpwmc_enable_write),
++};
++
++enum rkpwmc_count_id {
++ COUNT_PERIOD = 0,
++ COUNT_DUTY = 1,
++};
++
++static struct counter_count rkpwmc_counts[] = {
++ {
++ .id = COUNT_PERIOD,
++ .name = "Period in nanoseconds",
++ .functions_list = rkpwmc_functions,
++ .num_functions = ARRAY_SIZE(rkpwmc_functions),
++ .synapses = rkpwmc_pwm_synapses,
++ .num_synapses = ARRAY_SIZE(rkpwmc_pwm_synapses),
++ .ext = rkpwmc_ext,
++ .num_ext = ARRAY_SIZE(rkpwmc_ext),
++ },
++ {
++ .id = COUNT_DUTY,
++ .name = "Duty cycle in nanoseconds",
++ .functions_list = rkpwmc_functions,
++ .num_functions = ARRAY_SIZE(rkpwmc_functions),
++ .synapses = rkpwmc_pwm_synapses,
++ .num_synapses = ARRAY_SIZE(rkpwmc_pwm_synapses),
++ .ext = rkpwmc_ext,
++ .num_ext = ARRAY_SIZE(rkpwmc_ext),
++ },
++};
++
++static int rkpwmc_count_read(struct counter_device *counter,
++ struct counter_count *count, u64 *value)
++{
++ struct rockchip_pwm_capture *pc = counter_priv(counter);
++ unsigned long rate;
++ u64 period;
++ u64 lpc;
++ u64 hpc;
++ int ret = 0;
++
++ if (count->id != COUNT_PERIOD && count->id != COUNT_DUTY)
++ return -EINVAL;
++
++ ret = mfpwm_acquire(pc->pwmf);
++ if (ret)
++ return ret;
++
++ rate = mfpwm_clk_get_rate(pc->pwmf->parent);
++ if (!rate) {
++ ret = -EINVAL;
++ goto out_release;
++ }
++
++ hpc = (u32) atomic_read(&pc->hpc);
++
++ if (count->id == COUNT_PERIOD) {
++ lpc = (u32) atomic_read(&pc->lpc);
++ period = mult_frac((hpc + lpc), NSEC_PER_SEC, rate);
++ *value = period;
++ } else {
++ *value = mult_frac(hpc, NSEC_PER_SEC, rate);
++ }
++
++out_release:
++ mfpwm_release(pc->pwmf);
++
++ return ret;
++}
++
++static const struct counter_ops rkpwmc_ops = {
++ .count_read = rkpwmc_count_read,
++};
++
++static irqreturn_t rkpwmc_irq_handler(int irq, void *data)
++{
++ struct rockchip_pwm_capture *pc = data;
++ u32 intsts;
++ u32 clr = 0;
++ u32 lpc;
++ u32 hpc;
++
++ intsts = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_INTSTS);
++
++ if (!(intsts & RKPWMC_INT_MASK))
++ return IRQ_NONE;
++
++ if (intsts & PWMV4_INT_LPC) {
++ clr |= PWMV4_INT_LPC;
++ atomic_dec_if_positive(&pc->captures_left);
++ }
++
++ if (intsts & PWMV4_INT_HPC) {
++ clr |= PWMV4_INT_HPC;
++ atomic_dec_if_positive(&pc->captures_left);
++ }
++
++ /* After 4 interrupts, reset to 4 captures left and read the regs */
++ if (!atomic_cmpxchg(&pc->captures_left, 0, 4)) {
++ lpc = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_LPC);
++ hpc = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_HPC);
++
++ atomic_set(&pc->lpc, lpc);
++ atomic_set(&pc->hpc, hpc);
++ mod_delayed_work(system_bh_wq, &pc->clear_capture,
++ RKPWMC_CLEAR_DELAY);
++ }
++
++ if (clr)
++ mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INTSTS, clr);
++
++ if (intsts ^ clr)
++ return IRQ_NONE;
++
++ return IRQ_HANDLED;
++}
++
++static void rkpwmc_clear_capture_worker(struct work_struct *work)
++{
++ struct rockchip_pwm_capture *pc = container_of(
++ work, struct rockchip_pwm_capture, clear_capture.work);
++
++ atomic_set(&pc->hpc, 0);
++ atomic_set(&pc->lpc, 0);
++}
++
++static int rockchip_pwm_capture_probe(struct platform_device *pdev)
++{
++ struct rockchip_mfpwm_func *pwmf = dev_get_platdata(&pdev->dev);
++ struct rockchip_pwm_capture *pc;
++ struct counter_device *counter;
++ int ret;
++
++ counter = devm_counter_alloc(&pdev->dev, sizeof(*pc));
++ if (IS_ERR(counter))
++ return PTR_ERR(counter);
++
++ pc = counter_priv(counter);
++ pc->pwmf = pwmf;
++ spin_lock_init(&pc->enable_lock);
++
++ platform_set_drvdata(pdev, pc);
++
++ ret = devm_request_irq(&pdev->dev, pwmf->irq, rkpwmc_irq_handler,
++ IRQF_SHARED, pdev->name, pc);
++ if (ret)
++ return dev_err_probe(&pdev->dev, ret, "Failed requesting IRQ\n");
++
++ ret = devm_delayed_work_autocancel(&pdev->dev, &pc->clear_capture,
++ rkpwmc_clear_capture_worker);
++
++ counter->name = pdev->name;
++ counter->signals = rkpwmc_signals;
++ counter->num_signals = ARRAY_SIZE(rkpwmc_signals);
++ counter->ops = &rkpwmc_ops;
++ counter->counts = rkpwmc_counts;
++ counter->num_counts = ARRAY_SIZE(rkpwmc_counts);
++
++ ret = devm_counter_add(&pdev->dev, counter);
++ if (ret < 0)
++ return dev_err_probe(&pdev->dev, ret, "Failed to add counter\n");
++
++ return 0;
++}
++
++static void rockchip_pwm_capture_remove(struct platform_device *pdev)
++{
++ struct rockchip_mfpwm_func *pwmf = dev_get_platdata(&pdev->dev);
++
++ mfpwm_remove_func(pwmf);
++}
++
++static const struct platform_device_id rockchip_pwm_capture_id_table[] = {
++ { .name = "rockchip-pwm-capture", },
++ { /* sentinel */ },
++};
++MODULE_DEVICE_TABLE(platform, rockchip_pwm_capture_id_table);
++
++static struct platform_driver rockchip_pwm_capture_driver = {
++ .probe = rockchip_pwm_capture_probe,
++ .remove = rockchip_pwm_capture_remove,
++ .id_table = rockchip_pwm_capture_id_table,
++ .driver = {
++ .name = "rockchip-pwm-capture",
++ },
++};
++module_platform_driver(rockchip_pwm_capture_driver);
++
++MODULE_AUTHOR("Nicolas Frattaroli ");
++MODULE_DESCRIPTION("Rockchip PWM Counter Capture Driver");
++MODULE_LICENSE("GPL");
++MODULE_IMPORT_NS("ROCKCHIP_MFPWM");
++MODULE_IMPORT_NS("COUNTER");
+
+--
+2.49.0
diff --git a/lede/target/linux/rockchip/patches-6.12/341-07-pwm-pwmv4-add-device-set-of-node-from-parent.patch b/lede/target/linux/rockchip/patches-6.12/341-07-pwm-pwmv4-add-device-set-of-node-from-parent.patch
new file mode 100644
index 0000000000..9d1c2db8d3
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/341-07-pwm-pwmv4-add-device-set-of-node-from-parent.patch
@@ -0,0 +1,13 @@
+diff --git a/drivers/pwm/pwm-rockchip-v4.c b/drivers/pwm/pwm-rockchip-v4.c
+index 980b27454..3bc3d4979 100644
+--- a/drivers/pwm/pwm-rockchip-v4.c
++++ b/drivers/pwm/pwm-rockchip-v4.c
+@@ -292,6 +292,8 @@ static int rockchip_pwm_v4_probe(struct platform_device *pdev)
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
++ device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent);
++
+ pc = to_rockchip_pwm_v4(chip);
+ pc->pwmf = pwmf;
+
diff --git a/lede/target/linux/rockchip/patches-6.12/342-mmc-sdhci-of-dwcmshc-add-pd-workaround-on-rk3576.patch b/lede/target/linux/rockchip/patches-6.12/342-mmc-sdhci-of-dwcmshc-add-pd-workaround-on-rk3576.patch
new file mode 100644
index 0000000000..e9af302ef8
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/342-mmc-sdhci-of-dwcmshc-add-pd-workaround-on-rk3576.patch
@@ -0,0 +1,83 @@
+Signed-off-by: Nicolas Frattaroli
+---
+Changes in v2:
+- Rewrite patch to use dev_pm_genpd_rpm_always_on in sdhci driver
+ instead, after Ulf Hansson made me aware of its existence
+- Link to v1: https://lore.kernel.org/r/20250408-rk3576-emmc-fix-v1-1-3009828b1b31@collabora.com
+---
+ drivers/mmc/host/sdhci-of-dwcmshc.c | 39 +++++++++++++++++++++++++++++++++++++
+ 1 file changed, 39 insertions(+)
+
+diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c
+index 09b9ab15e4995f0bddf57dd309c010c849be40d9..a00aec05eff2da8197cc64690ba9665be756e54a 100644
+--- a/drivers/mmc/host/sdhci-of-dwcmshc.c
++++ b/drivers/mmc/host/sdhci-of-dwcmshc.c
+@@ -17,6 +17,7 @@
+ #include
+ #include
+ #include
++#include
+ #include
+ #include
+ #include
+@@ -745,6 +746,28 @@ static void dwcmshc_rk35xx_postinit(struct sdhci_host *host, struct dwcmshc_priv
+ }
+ }
+
++static void dwcmshc_rk3576_postinit(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv)
++{
++ struct device *dev = mmc_dev(host->mmc);
++ int ret;
++
++ /*
++ * This works around what appears to be a silicon bug, which makes the
++ * PD_NVM power domain, which the sdhci controller on the RK3576 is in,
++ * never come back the same way once it's turned off once. This can
++ * happen during early kernel boot if no driver is using either PD_NVM
++ * or its child power domain PD_SDGMAC for a short moment, leading to it
++ * being turned off to save power. By keeping it on, sdhci suspending
++ * won't lead to PD_NVM becoming a candidate for getting turned off.
++ */
++ ret = dev_pm_genpd_rpm_always_on(dev, true);
++ if (ret && ret != -EOPNOTSUPP)
++ dev_warn(dev, "failed to set PD rpm always on, SoC may hang later: %pe\n",
++ ERR_PTR(ret));
++
++ dwcmshc_rk35xx_postinit(host, dwc_priv);
++}
++
+ static int th1520_execute_tuning(struct sdhci_host *host, u32 opcode)
+ {
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+@@ -1176,6 +1199,18 @@ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_rk35xx_pdata = {
+ .postinit = dwcmshc_rk35xx_postinit,
+ };
+
++static const struct dwcmshc_pltfm_data sdhci_dwcmshc_rk3576_pdata = {
++ .pdata = {
++ .ops = &sdhci_dwcmshc_rk35xx_ops,
++ .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
++ SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
++ .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
++ SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
++ },
++ .init = dwcmshc_rk35xx_init,
++ .postinit = dwcmshc_rk3576_postinit,
++};
++
+ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_th1520_pdata = {
+ .pdata = {
+ .ops = &sdhci_dwcmshc_th1520_ops,
+@@ -1274,6 +1309,10 @@ static const struct of_device_id sdhci_dwcmshc_dt_ids[] = {
+ .compatible = "rockchip,rk3588-dwcmshc",
+ .data = &sdhci_dwcmshc_rk35xx_pdata,
+ },
++ {
++ .compatible = "rockchip,rk3576-dwcmshc",
++ .data = &sdhci_dwcmshc_rk3576_pdata,
++ },
+ {
+ .compatible = "rockchip,rk3568-dwcmshc",
+ .data = &sdhci_dwcmshc_rk35xx_pdata,
+
+---
diff --git a/lede/target/linux/rockchip/patches-6.12/993-add-rfkill-gpio-neo-driver.patch b/lede/target/linux/rockchip/patches-6.12/993-add-rfkill-gpio-neo-driver.patch
new file mode 100644
index 0000000000..54de03ce7a
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/993-add-rfkill-gpio-neo-driver.patch
@@ -0,0 +1,294 @@
+7diff --git a/net/rfkill/Kconfig b/net/rfkill/Kconfig
+index 83a7af898..b92e702d1 100644
+--- a/net/rfkill/Kconfig
++++ b/net/rfkill/Kconfig
+@@ -32,3 +32,12 @@ config RFKILL_GPIO
+ help
+ If you say yes here you get support of a generic gpio RFKILL
+ driver.
++
++config RFKILL_GPIO_NEO
++ tristate "Neo GPIO RFKILL driver"
++ depends on RFKILL_FULL
++ depends on OF_GPIO
++ default n
++ help
++ If you say yes here you get support of a new generic gpio RFKILL
++ driver.
+diff --git a/net/rfkill/Makefile b/net/rfkill/Makefile
+index dc47b6174..680302e72 100644
+--- a/net/rfkill/Makefile
++++ b/net/rfkill/Makefile
+@@ -7,3 +7,5 @@ rfkill-y += core.o
+ rfkill-$(CONFIG_RFKILL_INPUT) += input.o
+ obj-$(CONFIG_RFKILL) += rfkill.o
+ obj-$(CONFIG_RFKILL_GPIO) += rfkill-gpio.o
++
++obj-$(CONFIG_RFKILL_GPIO_NEO) += rfkill-gpio-neo.o
+diff --git a/net/rfkill/rfkill-gpio-neo.c b/net/rfkill/rfkill-gpio-neo.c
+new file mode 100644
+index 000000000..745e417e6
+--- /dev/null
++++ b/net/rfkill/rfkill-gpio-neo.c
+@@ -0,0 +1,261 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (c) 2022, Kyosuke Nekoyashiki
++ */
++
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++
++#define RFKILL_GPIO_NEO_THREADED_RESET 1
++
++struct rfkill_gpio_neo_data {
++ const char *name;
++ enum rfkill_type type;
++ struct gpio_desc *power_gpio;
++ struct gpio_desc *reset_gpio;
++ struct gpio_desc *block_gpio;
++
++ u32 power_on_wait_time;
++ u32 reset_active_time;
++ u32 reset_wait_time;
++ bool reset_working;
++
++ u32 block_state;
++ bool reset_before_block;
++
++ struct rfkill *rfkill_dev;
++};
++
++static int rfkill_gpio_neo_set_block(void *data, bool blocked)
++{
++ struct rfkill_gpio_neo_data *rfkill = data;
++
++ rfkill->block_state = blocked ? 1 : 0;
++
++ if (!rfkill->reset_working) {
++ if (rfkill->reset_before_block && rfkill->reset_gpio) {
++ gpiod_set_value(rfkill->reset_gpio, 1);
++ msleep(rfkill->reset_active_time);
++ if (!blocked) {
++ gpiod_set_value(rfkill->reset_gpio, 0);
++ if (rfkill->reset_wait_time > 10) {
++ msleep(rfkill->reset_wait_time);
++ } else {
++ msleep(10);
++ }
++ }
++ }
++ gpiod_set_value_cansleep(rfkill->block_gpio, blocked);
++ }
++
++ return 0;
++}
++
++static const struct rfkill_ops rfkill_gpio_neo_ops = {
++ .set_block = rfkill_gpio_neo_set_block,
++};
++
++
++static int rfkill_gpio_neo_do_reset(void *p) {
++ struct rfkill_gpio_neo_data *rfkill = (struct rfkill_gpio_neo_data *)p;
++
++ if (rfkill->power_on_wait_time > 10) {
++ msleep(rfkill->power_on_wait_time);
++ } else {
++ msleep(10);
++ }
++
++ gpiod_set_value(rfkill->reset_gpio, 1);
++ msleep(rfkill->reset_active_time);
++ gpiod_set_value(rfkill->reset_gpio, 0);
++
++ if (rfkill->reset_wait_time > 10) {
++ msleep(rfkill->reset_wait_time);
++ } else {
++ msleep(10);
++ }
++
++ rfkill->reset_working = 0;
++
++ gpiod_set_value(rfkill->block_gpio, rfkill->block_state);
++
++ return 0;
++}
++
++
++static int rfkill_gpio_neo_probe(struct platform_device *pdev)
++{
++ struct rfkill_gpio_neo_data *rfkill;
++ struct gpio_desc *gpio;
++ const char *type_name;
++ int ret;
++ struct task_struct *tsk;
++
++ rfkill = devm_kzalloc(&pdev->dev, sizeof(*rfkill), GFP_KERNEL);
++ if (!rfkill)
++ return -ENOMEM;
++
++ device_property_read_string(&pdev->dev, "name", &rfkill->name);
++ device_property_read_string(&pdev->dev, "type", &type_name);
++ device_property_read_u32(&pdev->dev, "power-on-wait-time", &rfkill->power_on_wait_time);
++ device_property_read_u32(&pdev->dev, "reset-active-time", &rfkill->reset_active_time);
++ device_property_read_u32(&pdev->dev, "reset-wait-time", &rfkill->reset_wait_time);
++ rfkill->reset_before_block = device_property_read_bool(&pdev->dev, "reset-before-block");
++
++ if (!rfkill->name)
++ rfkill->name = dev_name(&pdev->dev);
++
++ rfkill->type = rfkill_find_type(type_name);
++ rfkill->block_state = 0;
++ rfkill->reset_working = 0;
++
++ if (rfkill->power_on_wait_time > 30000) {
++ rfkill->power_on_wait_time = 0;
++ }
++
++ if (rfkill->reset_active_time < 10 || rfkill->reset_active_time > 1000) {
++ rfkill->reset_active_time = 10;
++ }
++
++ if (rfkill->reset_wait_time > 30000) {
++ rfkill->reset_wait_time = 0;
++ }
++
++ gpio = devm_gpiod_get(&pdev->dev, "power", GPIOD_OUT_LOW);
++ if (IS_ERR(gpio))
++ return PTR_ERR(gpio);
++
++ rfkill->power_gpio = gpio;
++
++ gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW);
++ if (IS_ERR(gpio))
++ return PTR_ERR(gpio);
++
++ rfkill->reset_gpio = gpio;
++
++ gpio = devm_gpiod_get(&pdev->dev, "block", GPIOD_OUT_HIGH);
++ if (IS_ERR(gpio))
++ return PTR_ERR(gpio);
++
++ rfkill->block_gpio = gpio;
++
++ /* Make sure at-least one GPIO is defined for this instance */
++ if (!rfkill->block_gpio) {
++ dev_err(&pdev->dev, "invalid platform data\n");
++ return -EINVAL;
++ }
++
++ rfkill->rfkill_dev = rfkill_alloc(rfkill->name, &pdev->dev,
++ rfkill->type, &rfkill_gpio_neo_ops,
++ rfkill);
++ if (!rfkill->rfkill_dev)
++ return -ENOMEM;
++
++ ret = rfkill_register(rfkill->rfkill_dev);
++ if (ret < 0)
++ goto err_destroy;
++
++ platform_set_drvdata(pdev, rfkill);
++
++ dev_info(&pdev->dev, "%s device registered.\n", rfkill->name);
++
++ if (rfkill->power_gpio) {
++ gpiod_set_value(rfkill->power_gpio, 1);
++ }
++
++ if (rfkill->reset_gpio) {
++ if (RFKILL_GPIO_NEO_THREADED_RESET && rfkill->power_on_wait_time > 10) {
++ tsk = kthread_run(rfkill_gpio_neo_do_reset, rfkill, "rfkill-gpio-neo");
++ if (IS_ERR(tsk)) {
++ dev_err(&pdev->dev, "Start reset thread failed!\n");
++ } else {
++ rfkill->reset_working = 1;
++ }
++ } else {
++ rfkill_gpio_neo_do_reset(rfkill);
++ }
++ }
++ else {
++ gpiod_set_value(rfkill->block_gpio, 0);
++ }
++
++ return 0;
++
++err_destroy:
++ rfkill_destroy(rfkill->rfkill_dev);
++
++ return ret;
++}
++
++static void rfkill_gpio_neo_remove(struct platform_device *pdev)
++{
++ struct rfkill_gpio_neo_data *rfkill = platform_get_drvdata(pdev);
++
++ if (rfkill->reset_gpio && rfkill->reset_before_block) {
++ gpiod_set_value(rfkill->reset_gpio, 1);
++ msleep(100);
++ }
++
++ gpiod_set_value(rfkill->block_gpio, 1);
++
++ if(rfkill->power_gpio) {
++ gpiod_set_value(rfkill->power_gpio, 0);
++ }
++
++ rfkill_unregister(rfkill->rfkill_dev);
++ rfkill_destroy(rfkill->rfkill_dev);
++}
++
++static void rfkill_gpio_neo_shutdown(struct platform_device *pdev)
++{
++ struct rfkill_gpio_neo_data *rfkill = platform_get_drvdata(pdev);
++
++ if (rfkill->reset_gpio && rfkill->reset_before_block) {
++ gpiod_set_value(rfkill->reset_gpio, 1);
++ msleep(100);
++ }
++
++ gpiod_set_value(rfkill->block_gpio, 1);
++
++ if(rfkill->power_gpio) {
++ gpiod_set_value(rfkill->power_gpio, 0);
++ }
++}
++
++#ifdef CONFIG_OF
++static struct of_device_id rfkill_gpio_neo_of_match[] = {
++ { .compatible = "rfkill-gpio-neo" },
++ {}
++};
++MODULE_DEVICE_TABLE(of, wlan_platdata_of_match);
++#endif /* CONFIG_OF */
++
++static struct platform_driver rfkill_gpio_neo_driver = {
++ .probe = rfkill_gpio_neo_probe,
++ .remove = rfkill_gpio_neo_remove,
++ .shutdown = rfkill_gpio_neo_shutdown,
++ .driver = {
++ .name = "rfkill-gpio-neo",
++ .owner = THIS_MODULE,
++ .of_match_table = of_match_ptr(rfkill_gpio_neo_of_match),
++ },
++};
++
++module_platform_driver(rfkill_gpio_neo_driver);
++
++MODULE_DESCRIPTION("Neo GPIO rfkill driver");
++MODULE_AUTHOR("Kyosuke Nekoyashiki ");
++MODULE_LICENSE("GPL v2");
++MODULE_ALIAS("platform:rfkill-gpio-neo");
diff --git a/lede/target/linux/rockchip/patches-6.12/996-set-led-mode-for-yt8521.patch b/lede/target/linux/rockchip/patches-6.12/996-set-led-mode-for-yt8521.patch
new file mode 100644
index 0000000000..21daad5d0d
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/996-set-led-mode-for-yt8521.patch
@@ -0,0 +1,46 @@
+--- a/drivers/net/phy/motorcomm.c 2025-02-14 18:58:18.691542738 +0900
++++ b/drivers/net/phy/motorcomm.c 2025-02-14 19:05:00.299135505 +0900
+@@ -353,6 +353,12 @@
+ #define YT8821_CHIP_MODE_AUTO_BX2500_SGMII 0
+ #define YT8821_CHIP_MODE_FORCE_BX2500 1
+
++#define YT8521_EXTREG_LED_GENERAL_CFG 0xA00B
++#define YT8521_EXTREG_LED0_CFG 0xA00C
++#define YT8521_EXTREG_LED1_CFG 0xA00D
++#define YT8521_EXTREG_LED2_CFG 0xA00E
++#define YT8521_EXTREG_LED_BLINK_CFG 0xA00F
++
+ struct yt8521_priv {
+ /* combo_advertising is used for case of YT8521 in combo mode,
+ * this means that yt8521 may work in utp or fiber mode which depends
+@@ -1577,6 +1583,20 @@
+ return 0;
+ }
+
++static int yt8521_led_init(struct phy_device *phydev)
++{
++ int ret;
++ u16 val;
++
++ val = (0x7 << 4);
++ ret = ytphy_write_ext(phydev, YT8521_EXTREG_LED2_CFG, val);
++
++ val = 0x7;
++ ret = ytphy_write_ext(phydev, YT8521_EXTREG_LED1_CFG, val);
++
++ return 0;
++}
++
+ /**
+ * yt8521_soft_reset() - called to issue a PHY software reset
+ * @phydev: a pointer to a &struct phy_device
+@@ -1677,6 +1697,9 @@
+ if (ret < 0)
+ goto err_restore_page;
+ }
++
++ yt8521_led_init(phydev);
++
+ err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+ }
diff --git a/lede/target/linux/rockchip/patches-6.12/997-rockchip-naneng-combo-phy-add-sgmii-mac-sel.patch b/lede/target/linux/rockchip/patches-6.12/997-rockchip-naneng-combo-phy-add-sgmii-mac-sel.patch
new file mode 100644
index 0000000000..b44785c4aa
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/997-rockchip-naneng-combo-phy-add-sgmii-mac-sel.patch
@@ -0,0 +1,38 @@
+--- a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c 2025-03-06 21:30:53.981108971 +0900
++++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c 2025-03-06 21:30:06.752107940 +0900
+@@ -138,6 +138,7 @@
+ struct combphy_reg pipe_xpcs_phy_ready;
+ struct combphy_reg pipe_pcie1l0_sel;
+ struct combphy_reg pipe_pcie1l1_sel;
++ struct combphy_reg pipe_sgmii_mac_sel;
+ };
+
+ struct rockchip_combphy_cfg {
+@@ -290,6 +291,7 @@
+
+ static int rockchip_combphy_parse_dt(struct device *dev, struct rockchip_combphy_priv *priv)
+ {
++ int mac_id;
+ int i;
+
+ priv->num_clks = devm_clk_bulk_get_all(dev, &priv->clks);
+@@ -325,6 +327,11 @@
+
+ priv->ext_refclk = device_property_present(dev, "rockchip,ext-refclk");
+
++ if (!device_property_read_u32(dev, "rockchip,sgmii-mac-sel", &mac_id)) {
++ rockchip_combphy_param_write(priv->pipe_grf, &priv->cfg->grfcfg->pipe_sgmii_mac_sel,
++ (mac_id > 0) ? true : false);
++ }
++
+ priv->phy_rst = devm_reset_control_get_exclusive(dev, "phy");
+ /* fallback to old behaviour */
+ if (PTR_ERR(priv->phy_rst) == -ENOENT)
+@@ -704,6 +711,7 @@
+ /* pipe-grf */
+ .pipe_con0_for_sata = { 0x0000, 15, 0, 0x00, 0x2220 },
+ .pipe_xpcs_phy_ready = { 0x0040, 2, 2, 0x00, 0x01 },
++ .pipe_sgmii_mac_sel = { 0x0040, 1, 1, 0x00, 0x01 },
+ };
+
+ static const struct rockchip_combphy_cfg rk3568_combphy_cfgs = {
diff --git a/lede/target/linux/x86/patches-6.12/900-add-CPU-model-number-for-Bartlett-Lake.patch b/lede/target/linux/x86/patches-6.12/900-add-CPU-model-number-for-Bartlett-Lake.patch
new file mode 100644
index 0000000000..5fcc3c1687
--- /dev/null
+++ b/lede/target/linux/x86/patches-6.12/900-add-CPU-model-number-for-Bartlett-Lake.patch
@@ -0,0 +1,11 @@
+--- a/arch/x86/include/asm/intel-family.h
++++ b/arch/x86/include/asm/intel-family.h
+@@ -126,6 +126,8 @@
+ #define INTEL_GRANITERAPIDS_X IFM(6, 0xAD) /* Redwood Cove */
+ #define INTEL_GRANITERAPIDS_D IFM(6, 0xAE)
+
++#define INTEL_RAPTORCOVE IFM(6, 0xD7) /* Bartlett Lake */
++
+ /* "Hybrid" Processors (P-Core/E-Core) */
+
+ #define INTEL_LAKEFIELD IFM(6, 0x8A) /* Sunny Cove / Tremont */
diff --git a/lede/target/linux/x86/patches-6.6/900-add-CPU-model-number-for-Bartlett-Lake.patch b/lede/target/linux/x86/patches-6.6/900-add-CPU-model-number-for-Bartlett-Lake.patch
new file mode 100644
index 0000000000..5fcc3c1687
--- /dev/null
+++ b/lede/target/linux/x86/patches-6.6/900-add-CPU-model-number-for-Bartlett-Lake.patch
@@ -0,0 +1,11 @@
+--- a/arch/x86/include/asm/intel-family.h
++++ b/arch/x86/include/asm/intel-family.h
+@@ -126,6 +126,8 @@
+ #define INTEL_GRANITERAPIDS_X IFM(6, 0xAD) /* Redwood Cove */
+ #define INTEL_GRANITERAPIDS_D IFM(6, 0xAE)
+
++#define INTEL_RAPTORCOVE IFM(6, 0xD7) /* Bartlett Lake */
++
+ /* "Hybrid" Processors (P-Core/E-Core) */
+
+ #define INTEL_LAKEFIELD IFM(6, 0x8A) /* Sunny Cove / Tremont */
diff --git a/mihomo/.github/workflows/test.yml b/mihomo/.github/workflows/test.yml
index def99d9373..db7ac0373e 100644
--- a/mihomo/.github/workflows/test.yml
+++ b/mihomo/.github/workflows/test.yml
@@ -37,6 +37,8 @@ jobs:
env:
CGO_ENABLED: 0
GOTOOLCHAIN: local
+ # Fix mingw trying to be smart and converting paths https://github.com/moby/moby/issues/24029#issuecomment-250412919
+ MSYS_NO_PATHCONV: true
steps:
- uses: actions/checkout@v4
diff --git a/mihomo/adapter/outbound/anytls.go b/mihomo/adapter/outbound/anytls.go
index 4727b1887f..7fb1d9c646 100644
--- a/mihomo/adapter/outbound/anytls.go
+++ b/mihomo/adapter/outbound/anytls.go
@@ -11,7 +11,6 @@ import (
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
- tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/anytls"
"github.com/metacubex/mihomo/transport/vmess"
@@ -115,9 +114,6 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
if tlsConfig.Host == "" {
tlsConfig.Host = option.Server
}
- if tlsC.HaveGlobalFingerprint() && len(option.ClientFingerprint) == 0 {
- tlsConfig.ClientFingerprint = tlsC.GetGlobalFingerprint()
- }
tOption.TLSConfig = tlsConfig
outbound := &AnyTLS{
diff --git a/mihomo/adapter/outbound/trojan.go b/mihomo/adapter/outbound/trojan.go
index 241666e52d..d6ca43794c 100644
--- a/mihomo/adapter/outbound/trojan.go
+++ b/mihomo/adapter/outbound/trojan.go
@@ -18,12 +18,13 @@ import (
"github.com/metacubex/mihomo/transport/gun"
"github.com/metacubex/mihomo/transport/shadowsocks/core"
"github.com/metacubex/mihomo/transport/trojan"
+ "github.com/metacubex/mihomo/transport/vmess"
)
type Trojan struct {
*Base
- instance *trojan.Trojan
- option *TrojanOption
+ option *TrojanOption
+ hexPassword [trojan.KeyLength]byte
// for gun mux
gunTLSConfig *tls.Config
@@ -61,15 +62,21 @@ type TrojanSSOption struct {
Password string `proxy:"password,omitempty"`
}
-func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) {
- if t.option.Network == "ws" {
+// StreamConnContext implements C.ProxyAdapter
+func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
+ switch t.option.Network {
+ case "ws":
host, port, _ := net.SplitHostPort(t.addr)
- wsOpts := &trojan.WebsocketOption{
+
+ wsOpts := &vmess.WebsocketConfig{
Host: host,
Port: port,
Path: t.option.WSOpts.Path,
+ MaxEarlyData: t.option.WSOpts.MaxEarlyData,
+ EarlyDataHeaderName: t.option.WSOpts.EarlyDataHeaderName,
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen,
+ ClientFingerprint: t.option.ClientFingerprint,
Headers: http.Header{},
}
@@ -83,35 +90,64 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error)
}
}
- return t.instance.StreamWebsocketConn(ctx, c, wsOpts)
- }
+ alpn := trojan.DefaultWebsocketALPN
+ if len(t.option.ALPN) != 0 {
+ alpn = t.option.ALPN
+ }
- return t.instance.StreamConn(ctx, c)
-}
+ wsOpts.TLS = true
+ tlsConfig := &tls.Config{
+ NextProtos: alpn,
+ MinVersion: tls.VersionTLS12,
+ InsecureSkipVerify: t.option.SkipCertVerify,
+ ServerName: t.option.SNI,
+ }
-// StreamConnContext implements C.ProxyAdapter
-func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
- var err error
+ wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint)
+ if err != nil {
+ return nil, err
+ }
- if tlsC.HaveGlobalFingerprint() && len(t.option.ClientFingerprint) == 0 {
- t.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
- }
-
- if t.transport != nil {
+ c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
+ case "grpc":
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig)
- } else {
- c, err = t.plainStream(ctx, c)
+ default:
+ // default tcp network
+ // handle TLS
+ alpn := trojan.DefaultALPN
+ if len(t.option.ALPN) != 0 {
+ alpn = t.option.ALPN
+ }
+ c, err = vmess.StreamTLSConn(ctx, c, &vmess.TLSConfig{
+ Host: t.option.SNI,
+ SkipCertVerify: t.option.SkipCertVerify,
+ FingerPrint: t.option.Fingerprint,
+ ClientFingerprint: t.option.ClientFingerprint,
+ NextProtos: alpn,
+ Reality: t.realityConfig,
+ })
}
-
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
+ return t.streamConnContext(ctx, c, metadata)
+}
+
+func (t *Trojan) streamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
- err = t.writeHeaderContext(ctx, c, metadata)
+ if ctx.Done() != nil {
+ done := N.SetupContextForConn(ctx, c)
+ defer done(&err)
+ }
+ command := trojan.CommandTCP
+ if metadata.NetWork == C.UDP {
+ command = trojan.CommandUDP
+ }
+ err = trojan.WriteHeader(c, t.hexPassword, command, serializesSocksAddr(metadata))
return c, err
}
@@ -124,25 +160,25 @@ func (t *Trojan) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C
if metadata.NetWork == C.UDP {
command = trojan.CommandUDP
}
- err = t.instance.WriteHeader(c, command, serializesSocksAddr(metadata))
+ err = trojan.WriteHeader(c, t.hexPassword, command, serializesSocksAddr(metadata))
return err
}
// DialContext implements C.ProxyAdapter
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
+ var c net.Conn
// gun transport
if t.transport != nil && dialer.IsZeroOptions(opts) {
- c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig)
+ c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
if err != nil {
return nil, err
}
+ defer func(c net.Conn) {
+ safeConnClose(c, err)
+ }(c)
- if t.ssCipher != nil {
- c = t.ssCipher.StreamConn(c)
- }
-
- if err = t.writeHeaderContext(ctx, c, metadata); err != nil {
- c.Close()
+ c, err = t.streamConnContext(ctx, c, metadata)
+ if err != nil {
return nil, err
}
@@ -190,16 +226,12 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
safeConnClose(c, err)
}(c)
- if t.ssCipher != nil {
- c = t.ssCipher.StreamConn(c)
- }
-
- err = t.writeHeaderContext(ctx, c, metadata)
+ c, err = t.streamConnContext(ctx, c, metadata)
if err != nil {
return nil, err
}
- pc := t.instance.PacketConn(c)
+ pc := trojan.NewPacketConn(c)
return newPacketConn(pc, t), err
}
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
@@ -220,21 +252,12 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
- c, err = t.plainStream(ctx, c)
- if err != nil {
- return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
- }
-
- if t.ssCipher != nil {
- c = t.ssCipher.StreamConn(c)
- }
-
- err = t.writeHeaderContext(ctx, c, metadata)
+ c, err = t.StreamConnContext(ctx, c, metadata)
if err != nil {
return nil, err
}
- pc := t.instance.PacketConn(c)
+ pc := trojan.NewPacketConn(c)
return newPacketConn(pc, t), err
}
@@ -245,7 +268,7 @@ func (t *Trojan) SupportWithDialer() C.NetWork {
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (t *Trojan) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
- pc := t.instance.PacketConn(c)
+ pc := trojan.NewPacketConn(c)
return newPacketConn(pc, t), err
}
@@ -272,19 +295,6 @@ func (t *Trojan) Close() error {
func NewTrojan(option TrojanOption) (*Trojan, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
- tOption := &trojan.Option{
- Password: option.Password,
- ALPN: option.ALPN,
- ServerName: option.Server,
- SkipCertVerify: option.SkipCertVerify,
- Fingerprint: option.Fingerprint,
- ClientFingerprint: option.ClientFingerprint,
- }
-
- if option.SNI != "" {
- tOption.ServerName = option.SNI
- }
-
t := &Trojan{
Base: &Base{
name: option.Name,
@@ -297,8 +307,8 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
- instance: trojan.New(tOption),
- option: &option,
+ option: &option,
+ hexPassword: trojan.Key(option.Password),
}
var err error
@@ -306,7 +316,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
if err != nil {
return nil, err
}
- tOption.Reality = t.realityConfig
if option.SSOpts.Enabled {
if option.SSOpts.Password == "" {
@@ -342,8 +351,8 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
tlsConfig := &tls.Config{
NextProtos: option.ALPN,
MinVersion: tls.VersionTLS12,
- InsecureSkipVerify: tOption.SkipCertVerify,
- ServerName: tOption.ServerName,
+ InsecureSkipVerify: option.SkipCertVerify,
+ ServerName: option.SNI,
}
var err error
@@ -352,13 +361,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
return nil, err
}
- t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig)
+ t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, option.ClientFingerprint, t.realityConfig)
t.gunTLSConfig = tlsConfig
t.gunConfig = &gun.Config{
ServiceName: option.GrpcOpts.GrpcServiceName,
- Host: tOption.ServerName,
- ClientFingerprint: tOption.ClientFingerprint,
+ Host: option.SNI,
+ ClientFingerprint: option.ClientFingerprint,
}
}
diff --git a/mihomo/adapter/outbound/vless.go b/mihomo/adapter/outbound/vless.go
index 079d7bc274..4d1a23b8e1 100644
--- a/mihomo/adapter/outbound/vless.go
+++ b/mihomo/adapter/outbound/vless.go
@@ -75,13 +75,7 @@ type VlessOption struct {
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
}
-func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
- var err error
-
- if tlsC.HaveGlobalFingerprint() && len(v.option.ClientFingerprint) == 0 {
- v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
- }
-
+func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
switch v.option.Network {
case "ws":
host, port, _ := net.SplitHostPort(v.addr)
@@ -232,9 +226,10 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
// DialContext implements C.ProxyAdapter
func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
+ var c net.Conn
// gun transport
if v.transport != nil && dialer.IsZeroOptions(opts) {
- c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
+ c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
diff --git a/mihomo/adapter/outbound/vmess.go b/mihomo/adapter/outbound/vmess.go
index 4db0ceddb5..fddef0e1b1 100644
--- a/mihomo/adapter/outbound/vmess.go
+++ b/mihomo/adapter/outbound/vmess.go
@@ -96,13 +96,7 @@ type WSOptions struct {
}
// StreamConnContext implements C.ProxyAdapter
-func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
- var err error
-
- if tlsC.HaveGlobalFingerprint() && (len(v.option.ClientFingerprint) == 0) {
- v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
- }
-
+func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
switch v.option.Network {
case "ws":
host, port, _ := net.SplitHostPort(v.addr)
@@ -226,10 +220,10 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
if err != nil {
return nil, err
}
- return v.streamConnConntext(ctx, c, metadata)
+ return v.streamConnContext(ctx, c, metadata)
}
-func (v *Vmess) streamConnConntext(ctx context.Context, c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
+func (v *Vmess) streamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
useEarly := N.NeedHandshake(c)
if !useEarly {
if ctx.Done() != nil {
@@ -287,9 +281,10 @@ func (v *Vmess) streamConnConntext(ctx context.Context, c net.Conn, metadata *C.
// DialContext implements C.ProxyAdapter
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
+ var c net.Conn
// gun transport
if v.transport != nil && dialer.IsZeroOptions(opts) {
- c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
+ c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
@@ -297,7 +292,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
safeConnClose(c, err)
}(c)
- c, err = v.streamConnConntext(ctx, c, metadata)
+ c, err = v.streamConnContext(ctx, c, metadata)
if err != nil {
return nil, err
}
@@ -348,7 +343,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
safeConnClose(c, err)
}(c)
- c, err = v.streamConnConntext(ctx, c, metadata)
+ c, err = v.streamConnContext(ctx, c, metadata)
if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err)
}
diff --git a/mihomo/adapter/parser.go b/mihomo/adapter/parser.go
index 48359f7052..56febe2817 100644
--- a/mihomo/adapter/parser.go
+++ b/mihomo/adapter/parser.go
@@ -5,7 +5,6 @@ import (
"github.com/metacubex/mihomo/adapter/outbound"
"github.com/metacubex/mihomo/common/structure"
- tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
)
@@ -22,7 +21,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
)
switch proxyType {
case "ss":
- ssOption := &outbound.ShadowSocksOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
+ ssOption := &outbound.ShadowSocksOption{}
err = decoder.Decode(mapping, ssOption)
if err != nil {
break
@@ -55,7 +54,6 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
Method: "GET",
Path: []string{"/"},
},
- ClientFingerprint: tlsC.GetGlobalFingerprint(),
}
err = decoder.Decode(mapping, vmessOption)
@@ -64,7 +62,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
}
proxy, err = outbound.NewVmess(*vmessOption)
case "vless":
- vlessOption := &outbound.VlessOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
+ vlessOption := &outbound.VlessOption{}
err = decoder.Decode(mapping, vlessOption)
if err != nil {
break
@@ -78,7 +76,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
}
proxy, err = outbound.NewSnell(*snellOption)
case "trojan":
- trojanOption := &outbound.TrojanOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
+ trojanOption := &outbound.TrojanOption{}
err = decoder.Decode(mapping, trojanOption)
if err != nil {
break
diff --git a/mihomo/component/dialer/dialer.go b/mihomo/component/dialer/dialer.go
index 6e1f242661..d7e7072aa5 100644
--- a/mihomo/component/dialer/dialer.go
+++ b/mihomo/component/dialer/dialer.go
@@ -20,34 +20,16 @@ const (
DefaultUDPTimeout = DefaultTCPTimeout
)
-type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error)
+type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error)
var (
dialMux sync.Mutex
- IP4PEnable bool
actualSingleStackDialContext = serialSingleStackDialContext
actualDualStackDialContext = serialDualStackDialContext
tcpConcurrent = false
fallbackTimeout = 300 * time.Millisecond
)
-func applyOptions(options ...Option) *option {
- opt := &option{
- interfaceName: DefaultInterface.Load(),
- routingMark: int(DefaultRoutingMark.Load()),
- }
-
- for _, o := range DefaultOptions {
- o(opt)
- }
-
- for _, o := range options {
- o(opt)
- }
-
- return opt
-}
-
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
opt := applyOptions(options...)
@@ -77,38 +59,43 @@ func DialContext(ctx context.Context, network, address string, options ...Option
}
func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort, options ...Option) (net.PacketConn, error) {
- cfg := applyOptions(options...)
+ opt := applyOptions(options...)
lc := &net.ListenConfig{}
- if cfg.addrReuse {
+ if opt.addrReuse {
addrReuseToListenConfig(lc)
}
if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CMFA)
socketHookToListenConfig(lc)
} else {
- interfaceName := cfg.interfaceName
- if interfaceName == "" {
+ if opt.interfaceName == "" {
+ opt.interfaceName = DefaultInterface.Load()
+ }
+ if opt.interfaceName == "" {
if finder := DefaultInterfaceFinder.Load(); finder != nil {
- interfaceName = finder.FindInterfaceName(rAddrPort.Addr())
+ opt.interfaceName = finder.FindInterfaceName(rAddrPort.Addr())
}
}
if rAddrPort.Addr().Unmap().IsLoopback() {
// avoid "The requested address is not valid in its context."
- interfaceName = ""
+ opt.interfaceName = ""
}
- if interfaceName != "" {
+ if opt.interfaceName != "" {
bind := bindIfaceToListenConfig
- if cfg.fallbackBind {
+ if opt.fallbackBind {
bind = fallbackBindIfaceToListenConfig
}
- addr, err := bind(interfaceName, lc, network, address, rAddrPort)
+ addr, err := bind(opt.interfaceName, lc, network, address, rAddrPort)
if err != nil {
return nil, err
}
address = addr
}
- if cfg.routingMark != 0 {
- bindMarkToListenConfig(cfg.routingMark, lc, network, address)
+ if opt.routingMark == 0 {
+ opt.routingMark = int(DefaultRoutingMark.Load())
+ }
+ if opt.routingMark != 0 {
+ bindMarkToListenConfig(opt.routingMark, lc, network, address)
}
}
@@ -134,7 +121,7 @@ func GetTcpConcurrent() bool {
return tcpConcurrent
}
-func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
+func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt option) (net.Conn, error) {
var address string
destination, port = resolver.LookupIP4P(destination, port)
address = net.JoinHostPort(destination.String(), port)
@@ -159,21 +146,26 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CMFA)
socketHookToToDialer(dialer)
} else {
- interfaceName := opt.interfaceName // don't change the "opt", it's a pointer
- if interfaceName == "" {
+ if opt.interfaceName == "" {
+ opt.interfaceName = DefaultInterface.Load()
+ }
+ if opt.interfaceName == "" {
if finder := DefaultInterfaceFinder.Load(); finder != nil {
- interfaceName = finder.FindInterfaceName(destination)
+ opt.interfaceName = finder.FindInterfaceName(destination)
}
}
- if interfaceName != "" {
+ if opt.interfaceName != "" {
bind := bindIfaceToDialer
if opt.fallbackBind {
bind = fallbackBindIfaceToDialer
}
- if err := bind(interfaceName, dialer, network, destination); err != nil {
+ if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
return nil, err
}
}
+ if opt.routingMark == 0 {
+ opt.routingMark = int(DefaultRoutingMark.Load())
+ }
if opt.routingMark != 0 {
bindMarkToDialer(opt.routingMark, dialer, network, destination)
}
@@ -185,26 +177,26 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
return dialer.DialContext(ctx, network, address)
}
-func serialSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
+func serialSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
return serialDialContext(ctx, network, ips, port, opt)
}
-func serialDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
+func serialDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
return dualStackDialContext(ctx, serialDialContext, network, ips, port, opt)
}
-func concurrentSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
+func concurrentSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
return parallelDialContext(ctx, network, ips, port, opt)
}
-func concurrentDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
+func concurrentDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
if opt.prefer != 4 && opt.prefer != 6 {
return parallelDialContext(ctx, network, ips, port, opt)
}
return dualStackDialContext(ctx, parallelDialContext, network, ips, port, opt)
}
-func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
+func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
ipv4s, ipv6s := resolver.SortationAddr(ips)
if len(ipv4s) == 0 && len(ipv6s) == 0 {
return nil, ErrorNoIpAddress
@@ -285,7 +277,7 @@ loop:
return nil, errors.Join(errs...)
}
-func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
+func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
if len(ips) == 0 {
return nil, ErrorNoIpAddress
}
@@ -324,7 +316,7 @@ func parallelDialContext(ctx context.Context, network string, ips []netip.Addr,
return nil, os.ErrDeadlineExceeded
}
-func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
+func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
if len(ips) == 0 {
return nil, ErrorNoIpAddress
}
@@ -390,5 +382,5 @@ func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddr
func NewDialer(options ...Option) Dialer {
opt := applyOptions(options...)
- return Dialer{Opt: *opt}
+ return Dialer{Opt: opt}
}
diff --git a/mihomo/component/dialer/options.go b/mihomo/component/dialer/options.go
index d15d36e80e..bb978cdba6 100644
--- a/mihomo/component/dialer/options.go
+++ b/mihomo/component/dialer/options.go
@@ -10,7 +10,6 @@ import (
)
var (
- DefaultOptions []Option
DefaultInterface = atomic.NewTypedValue[string]("")
DefaultRoutingMark = atomic.NewInt32(0)
@@ -117,9 +116,13 @@ func WithOption(o option) Option {
}
func IsZeroOptions(opts []Option) bool {
- var opt option
- for _, o := range opts {
+ return applyOptions(opts...) == option{}
+}
+
+func applyOptions(options ...Option) option {
+ opt := option{}
+ for _, o := range options {
o(&opt)
}
- return opt == option{}
+ return opt
}
diff --git a/mihomo/component/tls/reality.go b/mihomo/component/tls/reality.go
index 5beffb4392..eee37384a8 100644
--- a/mihomo/component/tls/reality.go
+++ b/mihomo/component/tls/reality.go
@@ -37,9 +37,9 @@ type RealityConfig struct {
ShortID [RealityMaxShortIDLen]byte
}
-func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
+func GetRealityConn(ctx context.Context, conn net.Conn, clientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
retry := 0
- for fingerprint, exists := GetFingerprint(ClientFingerprint); exists; retry++ {
+ for fingerprint, exists := GetFingerprint(clientFingerprint); exists; retry++ {
verifier := &realityVerifier{
serverName: tlsConfig.ServerName,
}
diff --git a/mihomo/listener/inbound/anytls_test.go b/mihomo/listener/inbound/anytls_test.go
index 5f295f795e..9d172890d7 100644
--- a/mihomo/listener/inbound/anytls_test.go
+++ b/mihomo/listener/inbound/anytls_test.go
@@ -19,17 +19,23 @@ func testInboundAnyTLS(t *testing.T, inboundOptions inbound.AnyTLSOption, outbou
}
inboundOptions.Users = map[string]string{"test": userUUID}
in, err := inbound.NewAnyTLS(&inboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
tunnel := NewHttpTestTunnel()
defer tunnel.Close()
err = in.Listen(tunnel)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer in.Close()
addrPort, err := netip.ParseAddrPort(in.Address())
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
outboundOptions.Name = "anytls_outbound"
outboundOptions.Server = addrPort.Addr().String()
@@ -37,7 +43,9 @@ func testInboundAnyTLS(t *testing.T, inboundOptions inbound.AnyTLSOption, outbou
outboundOptions.Password = userUUID
out, err := outbound.NewAnyTLS(outboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/mihomo/listener/inbound/common_test.go b/mihomo/listener/inbound/common_test.go
index 80ec006bff..18a6eefa53 100644
--- a/mihomo/listener/inbound/common_test.go
+++ b/mihomo/listener/inbound/common_test.go
@@ -132,7 +132,9 @@ func NewHttpTestTunnel() *TestTunnel {
go http.Serve(ln, r)
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
req = req.WithContext(ctx)
var dstPort uint16 = 80
@@ -145,7 +147,9 @@ func NewHttpTestTunnel() *TestTunnel {
DstPort: dstPort,
}
instance, err := proxy.DialContext(ctx, metadata)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer instance.Close()
transport := &http.Transport{
@@ -174,14 +178,18 @@ func NewHttpTestTunnel() *TestTunnel {
defer client.CloseIdleConnections()
resp, err := client.Do(req)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
data, err := io.ReadAll(resp.Body)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
assert.Equal(t, httpData, data)
}
tunnel := &TestTunnel{
@@ -212,27 +220,31 @@ func NewHttpTestTunnel() *TestTunnel {
CloseFn: ln.Close,
DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) {
// Sequential testing for debugging
- testFn(t, proxy, "http")
- testFn(t, proxy, "https")
+ t.Run("Sequential", func(t *testing.T) {
+ testFn(t, proxy, "http")
+ testFn(t, proxy, "https")
+ })
// Concurrent testing to detect stress
- wg := sync.WaitGroup{}
- num := 50
- for i := 0; i < num; i++ {
- wg.Add(1)
- go func() {
- testFn(t, proxy, "https")
- defer wg.Done()
- }()
- }
- for i := 0; i < num; i++ {
- wg.Add(1)
- go func() {
- testFn(t, proxy, "http")
- defer wg.Done()
- }()
- }
- wg.Wait()
+ t.Run("Concurrent", func(t *testing.T) {
+ wg := sync.WaitGroup{}
+ const num = 50
+ for i := 0; i < num; i++ {
+ wg.Add(1)
+ go func() {
+ testFn(t, proxy, "https")
+ defer wg.Done()
+ }()
+ }
+ for i := 0; i < num; i++ {
+ wg.Add(1)
+ go func() {
+ testFn(t, proxy, "http")
+ defer wg.Done()
+ }()
+ }
+ wg.Wait()
+ })
},
}
return tunnel
diff --git a/mihomo/listener/inbound/hysteria2_test.go b/mihomo/listener/inbound/hysteria2_test.go
index 13e4115c58..2926a9e6ca 100644
--- a/mihomo/listener/inbound/hysteria2_test.go
+++ b/mihomo/listener/inbound/hysteria2_test.go
@@ -19,17 +19,23 @@ func testInboundHysteria2(t *testing.T, inboundOptions inbound.Hysteria2Option,
}
inboundOptions.Users = map[string]string{"test": userUUID}
in, err := inbound.NewHysteria2(&inboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
tunnel := NewHttpTestTunnel()
defer tunnel.Close()
err = in.Listen(tunnel)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer in.Close()
addrPort, err := netip.ParseAddrPort(in.Address())
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
outboundOptions.Name = "hysteria2_outbound"
outboundOptions.Server = addrPort.Addr().String()
@@ -37,7 +43,9 @@ func testInboundHysteria2(t *testing.T, inboundOptions inbound.Hysteria2Option,
outboundOptions.Password = userUUID
out, err := outbound.NewHysteria2(outboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/mihomo/listener/inbound/mux_test.go b/mihomo/listener/inbound/mux_test.go
index 68c4a5aba4..4e676e6d96 100644
--- a/mihomo/listener/inbound/mux_test.go
+++ b/mihomo/listener/inbound/mux_test.go
@@ -32,7 +32,9 @@ func testSingMux(t *testing.T, tunnel *TestTunnel, out outbound.ProxyAdapter) {
Protocol: protocol,
}
out, err := outbound.NewSingMux(singMuxOption, ¬CloseProxyAdapter{out})
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/mihomo/listener/inbound/shadowsocks_test.go b/mihomo/listener/inbound/shadowsocks_test.go
index 770afbdd18..cf72c55c88 100644
--- a/mihomo/listener/inbound/shadowsocks_test.go
+++ b/mihomo/listener/inbound/shadowsocks_test.go
@@ -57,17 +57,23 @@ func testInboundShadowSocks0(t *testing.T, inboundOptions inbound.ShadowSocksOpt
}
inboundOptions.Password = password
in, err := inbound.NewShadowSocks(&inboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
tunnel := NewHttpTestTunnel()
defer tunnel.Close()
err = in.Listen(tunnel)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer in.Close()
addrPort, err := netip.ParseAddrPort(in.Address())
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
outboundOptions.Name = "shadowsocks_outbound"
outboundOptions.Server = addrPort.Addr().String()
@@ -75,7 +81,9 @@ func testInboundShadowSocks0(t *testing.T, inboundOptions inbound.ShadowSocksOpt
outboundOptions.Password = password
out, err := outbound.NewShadowSocks(outboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/mihomo/listener/inbound/trojan_test.go b/mihomo/listener/inbound/trojan_test.go
index c6ecbea8bc..320081f8c6 100644
--- a/mihomo/listener/inbound/trojan_test.go
+++ b/mihomo/listener/inbound/trojan_test.go
@@ -21,17 +21,23 @@ func testInboundTrojan(t *testing.T, inboundOptions inbound.TrojanOption, outbou
{Username: "test", Password: userUUID},
}
in, err := inbound.NewTrojan(&inboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
tunnel := NewHttpTestTunnel()
defer tunnel.Close()
err = in.Listen(tunnel)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer in.Close()
addrPort, err := netip.ParseAddrPort(in.Address())
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
outboundOptions.Name = "trojan_outbound"
outboundOptions.Server = addrPort.Addr().String()
@@ -39,7 +45,9 @@ func testInboundTrojan(t *testing.T, inboundOptions inbound.TrojanOption, outbou
outboundOptions.Password = userUUID
out, err := outbound.NewTrojan(outboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/mihomo/listener/inbound/tuic_test.go b/mihomo/listener/inbound/tuic_test.go
index 4b9753c83c..24865d8339 100644
--- a/mihomo/listener/inbound/tuic_test.go
+++ b/mihomo/listener/inbound/tuic_test.go
@@ -48,24 +48,32 @@ func testInboundTuic0(t *testing.T, inboundOptions inbound.TuicOption, outboundO
Port: "0",
}
in, err := inbound.NewTuic(&inboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
tunnel := NewHttpTestTunnel()
defer tunnel.Close()
err = in.Listen(tunnel)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer in.Close()
addrPort, err := netip.ParseAddrPort(in.Address())
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
outboundOptions.Name = "tuic_outbound"
outboundOptions.Server = addrPort.Addr().String()
outboundOptions.Port = int(addrPort.Port())
out, err := outbound.NewTuic(outboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/mihomo/listener/inbound/vless_test.go b/mihomo/listener/inbound/vless_test.go
index 04f441f173..f19cad348f 100644
--- a/mihomo/listener/inbound/vless_test.go
+++ b/mihomo/listener/inbound/vless_test.go
@@ -21,17 +21,23 @@ func testInboundVless(t *testing.T, inboundOptions inbound.VlessOption, outbound
{Username: "test", UUID: userUUID, Flow: "xtls-rprx-vision"},
}
in, err := inbound.NewVless(&inboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
tunnel := NewHttpTestTunnel()
defer tunnel.Close()
err = in.Listen(tunnel)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer in.Close()
addrPort, err := netip.ParseAddrPort(in.Address())
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
outboundOptions.Name = "vless_outbound"
outboundOptions.Server = addrPort.Addr().String()
@@ -39,7 +45,9 @@ func testInboundVless(t *testing.T, inboundOptions inbound.VlessOption, outbound
outboundOptions.UUID = userUUID
out, err := outbound.NewVless(outboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/mihomo/listener/inbound/vmess_test.go b/mihomo/listener/inbound/vmess_test.go
index a9b99de7d3..57af5b0b90 100644
--- a/mihomo/listener/inbound/vmess_test.go
+++ b/mihomo/listener/inbound/vmess_test.go
@@ -21,17 +21,23 @@ func testInboundVMess(t *testing.T, inboundOptions inbound.VmessOption, outbound
{Username: "test", UUID: userUUID, AlterID: 0},
}
in, err := inbound.NewVmess(&inboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
tunnel := NewHttpTestTunnel()
defer tunnel.Close()
err = in.Listen(tunnel)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer in.Close()
addrPort, err := netip.ParseAddrPort(in.Address())
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
outboundOptions.Name = "vmess_outbound"
outboundOptions.Server = addrPort.Addr().String()
@@ -41,7 +47,9 @@ func testInboundVMess(t *testing.T, inboundOptions inbound.VmessOption, outbound
outboundOptions.Cipher = "auto"
out, err := outbound.NewVmess(outboundOptions)
- assert.NoError(t, err)
+ if !assert.NoError(t, err) {
+ return
+ }
defer out.Close()
tunnel.DoTest(t, out)
diff --git a/mihomo/transport/anytls/client.go b/mihomo/transport/anytls/client.go
index ea99b43883..abd8364a0c 100644
--- a/mihomo/transport/anytls/client.go
+++ b/mihomo/transport/anytls/client.go
@@ -9,7 +9,6 @@ import (
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/buf"
- C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/anytls/padding"
"github.com/metacubex/mihomo/transport/anytls/session"
"github.com/metacubex/mihomo/transport/vmess"
@@ -83,12 +82,7 @@ func (c *Client) CreateOutboundTLSConnection(ctx context.Context) (net.Conn, err
b.WriteZeroN(paddingLen)
}
- getTlsConn := func() (net.Conn, error) {
- ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
- defer cancel()
- return vmess.StreamTLSConn(ctx, conn, c.tlsConfig)
- }
- tlsConn, err := getTlsConn()
+ tlsConn, err := vmess.StreamTLSConn(ctx, conn, c.tlsConfig)
if err != nil {
conn.Close()
return nil, err
diff --git a/mihomo/transport/gun/gun.go b/mihomo/transport/gun/gun.go
index b9f03ce762..8889baa753 100644
--- a/mihomo/transport/gun/gun.go
+++ b/mihomo/transport/gun/gun.go
@@ -224,7 +224,7 @@ func (g *Conn) SetDeadline(t time.Time) error {
return nil
}
-func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, realityConfig *tlsC.RealityConfig) *TransportWrap {
+func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, clientFingerprint string, realityConfig *tlsC.RealityConfig) *TransportWrap {
dialFunc := func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
defer cancel()
@@ -237,9 +237,13 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
return pconn, nil
}
- if len(Fingerprint) != 0 {
+ clientFingerprint := clientFingerprint
+ if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
+ clientFingerprint = tlsC.GetGlobalFingerprint()
+ }
+ if len(clientFingerprint) != 0 {
if realityConfig == nil {
- if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists {
+ if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
utlsConn := tlsC.UClient(pconn, cfg, fingerprint)
if err := utlsConn.HandshakeContext(ctx); err != nil {
pconn.Close()
@@ -253,7 +257,7 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
return utlsConn, nil
}
} else {
- realityConn, err := tlsC.GetRealityConn(ctx, pconn, Fingerprint, cfg, realityConfig)
+ realityConn, err := tlsC.GetRealityConn(ctx, pconn, clientFingerprint, cfg, realityConfig)
if err != nil {
pconn.Close()
return nil, err
diff --git a/mihomo/transport/sing-shadowtls/shadowtls.go b/mihomo/transport/sing-shadowtls/shadowtls.go
index 7bfd45787f..a628e7b168 100644
--- a/mihomo/transport/sing-shadowtls/shadowtls.go
+++ b/mihomo/transport/sing-shadowtls/shadowtls.go
@@ -45,13 +45,7 @@ func NewShadowTLS(ctx context.Context, conn net.Conn, option *ShadowTLSOption) (
return nil, err
}
- var clientHelloID utls.ClientHelloID
- if len(option.ClientFingerprint) != 0 {
- if fingerprint, exists := tlsC.GetFingerprint(option.ClientFingerprint); exists {
- clientHelloID = *fingerprint.ClientHelloID
- }
- }
- tlsHandshake := uTLSHandshakeFunc(tlsConfig, clientHelloID)
+ tlsHandshake := uTLSHandshakeFunc(tlsConfig, option.ClientFingerprint)
client, err := shadowtls.NewClient(shadowtls.ClientConfig{
Version: option.Version,
Password: option.Password,
@@ -64,7 +58,7 @@ func NewShadowTLS(ctx context.Context, conn net.Conn, option *ShadowTLSOption) (
return client.DialContextConn(ctx, conn)
}
-func uTLSHandshakeFunc(config *tls.Config, clientHelloID utls.ClientHelloID) shadowtls.TLSHandshakeFunc {
+func uTLSHandshakeFunc(config *tls.Config, clientFingerprint string) shadowtls.TLSHandshakeFunc {
return func(ctx context.Context, conn net.Conn, sessionIDGenerator shadowtls.TLSSessionIDGeneratorFunc) error {
tlsConfig := &utls.Config{
Rand: config.Rand,
@@ -84,12 +78,18 @@ func uTLSHandshakeFunc(config *tls.Config, clientHelloID utls.ClientHelloID) sha
Renegotiation: utls.RenegotiationSupport(config.Renegotiation),
SessionIDGenerator: sessionIDGenerator,
}
- var empty utls.ClientHelloID
- if clientHelloID == empty {
- tlsConn := utls.Client(conn, tlsConfig)
- return tlsConn.Handshake()
+ clientFingerprint := clientFingerprint
+ if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
+ clientFingerprint = tlsC.GetGlobalFingerprint()
}
- tlsConn := utls.UClient(conn, tlsConfig, clientHelloID)
+ if len(clientFingerprint) != 0 {
+ if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
+ clientHelloID := *fingerprint.ClientHelloID
+ tlsConn := utls.UClient(conn, tlsConfig, clientHelloID)
+ return tlsConn.HandshakeContext(ctx)
+ }
+ }
+ tlsConn := utls.Client(conn, tlsConfig)
return tlsConn.HandshakeContext(ctx)
}
}
diff --git a/mihomo/transport/trojan/trojan.go b/mihomo/transport/trojan/trojan.go
index e500050238..93819130fd 100644
--- a/mihomo/transport/trojan/trojan.go
+++ b/mihomo/transport/trojan/trojan.go
@@ -1,24 +1,17 @@
package trojan
import (
- "context"
"crypto/sha256"
- "crypto/tls"
"encoding/binary"
"encoding/hex"
"errors"
"io"
"net"
- "net/http"
"sync"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/pool"
- "github.com/metacubex/mihomo/component/ca"
- tlsC "github.com/metacubex/mihomo/component/tls"
- C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5"
- "github.com/metacubex/mihomo/transport/vmess"
)
const (
@@ -27,8 +20,8 @@ const (
)
var (
- defaultALPN = []string{"h2", "http/1.1"}
- defaultWebsocketALPN = []string{"http/1.1"}
+ DefaultALPN = []string{"h2", "http/1.1"}
+ DefaultWebsocketALPN = []string{"http/1.1"}
crlf = []byte{'\r', '\n'}
)
@@ -43,115 +36,11 @@ const (
KeyLength = 56
)
-type Option struct {
- Password string
- ALPN []string
- ServerName string
- SkipCertVerify bool
- Fingerprint string
- ClientFingerprint string
- Reality *tlsC.RealityConfig
-}
-
-type WebsocketOption struct {
- Host string
- Port string
- Path string
- Headers http.Header
- V2rayHttpUpgrade bool
- V2rayHttpUpgradeFastOpen bool
-}
-
-type Trojan struct {
- option *Option
- hexPassword [KeyLength]byte
-}
-
-func (t *Trojan) StreamConn(ctx context.Context, conn net.Conn) (net.Conn, error) {
- alpn := defaultALPN
- if len(t.option.ALPN) != 0 {
- alpn = t.option.ALPN
- }
- tlsConfig := &tls.Config{
- NextProtos: alpn,
- MinVersion: tls.VersionTLS12,
- InsecureSkipVerify: t.option.SkipCertVerify,
- ServerName: t.option.ServerName,
- }
-
- var err error
- tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint)
- if err != nil {
- return nil, err
- }
-
- if len(t.option.ClientFingerprint) != 0 {
- if t.option.Reality == nil {
- utlsConn, valid := vmess.GetUTLSConn(conn, t.option.ClientFingerprint, tlsConfig)
- if valid {
- ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
- defer cancel()
-
- err := utlsConn.HandshakeContext(ctx)
- return utlsConn, err
- }
- } else {
- ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
- defer cancel()
- return tlsC.GetRealityConn(ctx, conn, t.option.ClientFingerprint, tlsConfig, t.option.Reality)
- }
- }
- if t.option.Reality != nil {
- return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
- }
-
- tlsConn := tls.Client(conn, tlsConfig)
-
- // fix tls handshake not timeout
- ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
- defer cancel()
-
- err = tlsConn.HandshakeContext(ctx)
- return tlsConn, err
-}
-
-func (t *Trojan) StreamWebsocketConn(ctx context.Context, conn net.Conn, wsOptions *WebsocketOption) (net.Conn, error) {
- alpn := defaultWebsocketALPN
- if len(t.option.ALPN) != 0 {
- alpn = t.option.ALPN
- }
-
- tlsConfig := &tls.Config{
- NextProtos: alpn,
- MinVersion: tls.VersionTLS12,
- InsecureSkipVerify: t.option.SkipCertVerify,
- ServerName: t.option.ServerName,
- }
-
- var err error
- tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint)
- if err != nil {
- return nil, err
- }
-
- return vmess.StreamWebsocketConn(ctx, conn, &vmess.WebsocketConfig{
- Host: wsOptions.Host,
- Port: wsOptions.Port,
- Path: wsOptions.Path,
- Headers: wsOptions.Headers,
- V2rayHttpUpgrade: wsOptions.V2rayHttpUpgrade,
- V2rayHttpUpgradeFastOpen: wsOptions.V2rayHttpUpgradeFastOpen,
- TLS: true,
- TLSConfig: tlsConfig,
- ClientFingerprint: t.option.ClientFingerprint,
- })
-}
-
-func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error {
+func WriteHeader(w io.Writer, hexPassword [KeyLength]byte, command Command, socks5Addr []byte) error {
buf := pool.GetBuffer()
defer pool.PutBuffer(buf)
- buf.Write(t.hexPassword[:])
+ buf.Write(hexPassword[:])
buf.Write(crlf)
buf.WriteByte(command)
@@ -162,12 +51,6 @@ func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) er
return err
}
-func (t *Trojan) PacketConn(conn net.Conn) net.PacketConn {
- return &PacketConn{
- Conn: conn,
- }
-}
-
func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
buf := pool.GetBuffer()
defer pool.PutBuffer(buf)
@@ -243,10 +126,6 @@ func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, int, error) {
return uAddr, length, total - length, nil
}
-func New(option *Option) *Trojan {
- return &Trojan{option, Key(option.Password)}
-}
-
var _ N.EnhancePacketConn = (*PacketConn)(nil)
type PacketConn struct {
diff --git a/mihomo/transport/vmess/tls.go b/mihomo/transport/vmess/tls.go
index 82a484f1b8..ff622995a0 100644
--- a/mihomo/transport/vmess/tls.go
+++ b/mihomo/transport/vmess/tls.go
@@ -32,15 +32,22 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn
return nil, err
}
- if len(cfg.ClientFingerprint) != 0 {
+ clientFingerprint := cfg.ClientFingerprint
+ if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
+ clientFingerprint = tlsC.GetGlobalFingerprint()
+ }
+ if len(clientFingerprint) != 0 {
if cfg.Reality == nil {
- utlsConn, valid := GetUTLSConn(conn, cfg.ClientFingerprint, tlsConfig)
- if valid {
+ if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
+ utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
err = utlsConn.HandshakeContext(ctx)
- return utlsConn, err
+ if err != nil {
+ return nil, err
+ }
+ return utlsConn, nil
}
} else {
- return tlsC.GetRealityConn(ctx, conn, cfg.ClientFingerprint, tlsConfig, cfg.Reality)
+ return tlsC.GetRealityConn(ctx, conn, clientFingerprint, tlsConfig, cfg.Reality)
}
}
if cfg.Reality != nil {
@@ -52,14 +59,3 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn
err = tlsConn.HandshakeContext(ctx)
return tlsConn, err
}
-
-func GetUTLSConn(conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config) (*tlsC.UConn, bool) {
-
- if fingerprint, exists := tlsC.GetFingerprint(ClientFingerprint); exists {
- utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
-
- return utlsConn, true
- }
-
- return nil, false
-}
diff --git a/mihomo/transport/vmess/websocket.go b/mihomo/transport/vmess/websocket.go
index 8ada88ecc1..43b695ee93 100644
--- a/mihomo/transport/vmess/websocket.go
+++ b/mihomo/transport/vmess/websocket.go
@@ -354,8 +354,12 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig,
config.ServerName = uri.Host
}
- if len(c.ClientFingerprint) != 0 {
- if fingerprint, exists := tlsC.GetFingerprint(c.ClientFingerprint); exists {
+ clientFingerprint := c.ClientFingerprint
+ if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
+ clientFingerprint = tlsC.GetGlobalFingerprint()
+ }
+ if len(clientFingerprint) != 0 {
+ if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
utlsConn := tlsC.UClient(conn, config, fingerprint)
if err = utlsConn.BuildWebsocketHandshakeState(); err != nil {
return nil, fmt.Errorf("parse url %s error: %w", c.Path, err)
diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
index 425cd70d5d..9672c449e4 100644
--- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
+++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
@@ -365,15 +365,15 @@ o:value("119.28.28.28")
o:depends("direct_dns_mode", "tcp")
o = s:taboption("DNS", Value, "direct_dns_dot", translate("Direct DNS DoT"))
-o.default = "tls://1.12.12.12"
-o:value("tls://1.12.12.12")
-o:value("tls://120.53.53.53")
-o:value("tls://36.99.170.86")
-o:value("tls://101.198.191.4")
-o:value("tls://223.5.5.5")
-o:value("tls://223.6.6.6")
-o:value("tls://2400:3200::1")
-o:value("tls://2400:3200:baba::1")
+o.default = "tls://dot.pub@1.12.12.12"
+o:value("tls://dot.pub@1.12.12.12")
+o:value("tls://dot.pub@120.53.53.53")
+o:value("tls://dot.360.cn@36.99.170.86")
+o:value("tls://dot.360.cn@101.198.191.4")
+o:value("tls://dns.alidns.com@223.5.5.5")
+o:value("tls://dns.alidns.com@223.6.6.6")
+o:value("tls://dns.alidns.com@2400:3200::1")
+o:value("tls://dns.alidns.com@2400:3200:baba::1")
o.validate = chinadns_dot_validate
o:depends("direct_dns_mode", "dot")
@@ -515,17 +515,17 @@ o:depends({singbox_dns_mode = "tcp"})
---- DoT
o = s:taboption("DNS", Value, "remote_dns_dot", translate("Remote DNS DoT"))
-o.default = "tls://1.1.1.1"
-o:value("tls://1.0.0.1", "1.0.0.1 (CloudFlare)")
-o:value("tls://1.1.1.1", "1.1.1.1 (CloudFlare)")
-o:value("tls://8.8.4.4", "8.8.4.4 (Google)")
-o:value("tls://8.8.8.8", "8.8.8.8 (Google)")
-o:value("tls://9.9.9.9", "9.9.9.9 (Quad9)")
-o:value("tls://149.112.112.112", "149.112.112.112 (Quad9)")
-o:value("tls://94.140.14.14", "94.140.14.14 (AdGuard)")
-o:value("tls://94.140.15.15", "94.140.15.15 (AdGuard)")
-o:value("tls://208.67.222.222", "208.67.222.222 (OpenDNS)")
-o:value("tls://208.67.220.220", "208.67.220.220 (OpenDNS)")
+o.default = "tls://one.one.one.one@1.1.1.1"
+o:value("tls://one.one.one.one@1.0.0.1", "1.0.0.1 (CloudFlare)")
+o:value("tls://one.one.one.one@1.1.1.1", "1.1.1.1 (CloudFlare)")
+o:value("tls://dns.google@8.8.4.4", "8.8.4.4 (Google)")
+o:value("tls://dns.google@8.8.8.8", "8.8.8.8 (Google)")
+o:value("tls://dns.quad9.net@9.9.9.9", "9.9.9.9 (Quad9)")
+o:value("tls://dns.quad9.net@149.112.112.112", "149.112.112.112 (Quad9)")
+o:value("tls://dns.adguard.com@94.140.14.14", "94.140.14.14 (AdGuard)")
+o:value("tls://dns.adguard.com@94.140.15.15", "94.140.15.15 (AdGuard)")
+o:value("tls://dns.opendns.com@208.67.222.222", "208.67.222.222 (OpenDNS)")
+o:value("tls://dns.opendns.com@208.67.220.220", "208.67.220.220 (OpenDNS)")
o.validate = chinadns_dot_validate
o:depends("dns_mode", "dot")
@@ -604,6 +604,11 @@ if api.is_finded("smartdns") then
o:depends({dns_shunt = "smartdns", tcp_proxy_mode = "proxy", chn_list = "direct"})
end
+o = s:taboption("DNS", Flag, "chinadns_ng_cert_verify", translate("DoT Cert verify"), translate("Verify DoT SSL cert. (May fail on some platforms!)"))
+o.default = "0"
+o:depends({direct_dns_mode = "dot"})
+o:depends({dns_mode = "dot"})
+
o = s:taboption("DNS", Flag, "dns_redirect", translate("DNS Redirect"), translate("Force special DNS server to need proxy devices."))
o.default = "1"
o.rmempty = false
diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
index 61dd830f99..208f7e3621 100644
--- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
+++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
@@ -27,7 +27,7 @@ local show_node_info = m:get("@global_other[0]", "show_node_info") or "0"
if auto_detection_time ~= "0" then
local node_count = 0
for k, e in ipairs(api.get_valid_nodes()) do
- if e.protocol ~= "_shunt" and e.protocol ~= "_balancing" and e.protocol ~= "_urltest" and e.protocol ~= "_iface" then
+ if e.node_type == "normal" then
node_count = node_count + 1
end
end
@@ -168,8 +168,10 @@ o = s:option(DummyValue, "ping", "Ping")
o.width = "8%"
o.rawhtml = true
o.cfgvalue = function(t, n)
- local protocol = m:get(n, "protocol")
- if protocol == "_shunt" or protocol == "_balancing" or protocol == "_urltest" or protocol == "_iface" then
+ local type = m:get(n, "type") or ""
+ local protocol = m:get(n, "protocol") or ""
+ if (type == "sing-box" or type == "Xray") and
+ (protocol == "_shunt" or protocol == "_balancing" or protocol == "_urltest" or protocol == "_iface") then
return string.format('---', n)
end
if auto_detection_time ~= "icmp" then
@@ -184,8 +186,10 @@ o = s:option(DummyValue, "tcping", "TCPing")
o.width = "8%"
o.rawhtml = true
o.cfgvalue = function(t, n)
- local protocol = m:get(n, "protocol")
- if protocol == "_shunt" or protocol == "_balancing" or protocol == "_urltest" or protocol == "_iface" then
+ local type = m:get(n, "type") or ""
+ local protocol = m:get(n, "protocol") or ""
+ if (type == "sing-box" or type == "Xray") and
+ (protocol == "_shunt" or protocol == "_balancing" or protocol == "_urltest" or protocol == "_iface") then
return string.format('---', n)
end
if auto_detection_time ~= "tcping" then
diff --git a/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po b/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po
index a93efa4449..c28a4e6cad 100644
--- a/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po
+++ b/openwrt-passwall/luci-app-passwall/po/zh-cn/passwall.po
@@ -232,6 +232,12 @@ msgstr "清空 IPSET"
msgid "Clear NFTSET"
msgstr "清空 NFTSET"
+msgid "DoT Cert verify"
+msgstr "DoT 证书验证"
+
+msgid "Verify DoT SSL cert. (May fail on some platforms!)"
+msgstr "验证 DoT SSL 证书。(在某些平台可能无法验证,谨慎开启!)"
+
msgid "Try this feature if the rule modification does not take effect."
msgstr "如果修改规则后没有生效,请尝试此功能。"
diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh
index 0c5321d245..50a8dec291 100755
--- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh
+++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh
@@ -933,7 +933,7 @@ run_redir() {
_args="${_args} direct_dns_tcp_server=$(config_t_get global direct_dns_tcp 223.5.5.5 | sed 's/:/#/g')"
;;
dot)
- local tmp_dot_dns=$(config_t_get global direct_dns_dot "tls://1.12.12.12")
+ local tmp_dot_dns=$(config_t_get global direct_dns_dot "tls://dot.pub@1.12.12.12")
local tmp_dot_ip=$(echo "$tmp_dot_dns" | sed -n 's/.*:\/\/\([^@#]*@\)*\([^@#]*\).*/\2/p')
local tmp_dot_port=$(echo "$tmp_dot_dns" | sed -n 's/.*#\([0-9]\+\).*/\1/p')
_args="${_args} direct_dns_dot_server=$tmp_dot_ip#${tmp_dot_port:-853}"
@@ -1420,13 +1420,14 @@ start_dns() {
;;
dot)
if [ "$chinadns_tls" != "nil" ]; then
- local DIRECT_DNS=$(config_t_get global direct_dns_dot "tls://1.12.12.12")
+ local DIRECT_DNS=$(config_t_get global direct_dns_dot "tls://dot.pub@1.12.12.12")
+ local cert_verify=$([ "$(config_t_get global chinadns_ng_cert_verify 0)" = "1" ] && echo "--cert-verify")
china_ng_local_dns=${DIRECT_DNS}
#当全局(包括访问控制节点)开启chinadns-ng时,不启动新进程。
[ "$DNS_SHUNT" != "chinadns-ng" ] || [ "$ACL_RULE_DNSMASQ" = "1" ] && {
LOCAL_DNS="127.0.0.1#${NEXT_DNS_LISTEN_PORT}"
- ln_run "$(first_type chinadns-ng)" chinadns-ng "/dev/null" -b 127.0.0.1 -l ${NEXT_DNS_LISTEN_PORT} -c ${DIRECT_DNS} -d chn
+ ln_run "$(first_type chinadns-ng)" chinadns-ng "/dev/null" -b 127.0.0.1 -l ${NEXT_DNS_LISTEN_PORT} -c ${DIRECT_DNS} -d chn ${cert_verify}
echolog " - ChinaDNS-NG(${LOCAL_DNS}) -> ${DIRECT_DNS}"
echolog " * 请确保上游直连 DNS 支持 DoT 查询。"
NEXT_DNS_LISTEN_PORT=$(expr $NEXT_DNS_LISTEN_PORT + 1)
@@ -1542,13 +1543,14 @@ start_dns() {
TCP_PROXY_DNS=1
if [ "$chinadns_tls" != "nil" ]; then
local china_ng_listen_port=${NEXT_DNS_LISTEN_PORT}
- local china_ng_trust_dns=$(config_t_get global remote_dns_dot "tls://1.1.1.1")
+ local china_ng_trust_dns=$(config_t_get global remote_dns_dot "tls://one.one.one.one@1.1.1.1")
+ local cert_verify=$([ "$(config_t_get global chinadns_ng_cert_verify 0)" = "1" ] && echo "--cert-verify")
local tmp_dot_ip=$(echo "$china_ng_trust_dns" | sed -n 's/.*:\/\/\([^@#]*@\)*\([^@#]*\).*/\2/p')
local tmp_dot_port=$(echo "$china_ng_trust_dns" | sed -n 's/.*#\([0-9]\+\).*/\1/p')
REMOTE_DNS="$tmp_dot_ip#${tmp_dot_port:-853}"
[ "$DNS_SHUNT" != "chinadns-ng" ] && {
[ "$FILTER_PROXY_IPV6" = "1" ] && DNSMASQ_FILTER_PROXY_IPV6=0 && local no_ipv6_trust="-N"
- ln_run "$(first_type chinadns-ng)" chinadns-ng "/dev/null" -b 127.0.0.1 -l ${china_ng_listen_port} -t ${china_ng_trust_dns} -d gfw ${no_ipv6_trust}
+ ln_run "$(first_type chinadns-ng)" chinadns-ng "/dev/null" -b 127.0.0.1 -l ${china_ng_listen_port} -t ${china_ng_trust_dns} -d gfw ${no_ipv6_trust} ${cert_verify}
echolog " - ChinaDNS-NG(${TUN_DNS}) -> ${china_ng_trust_dns}"
}
else
@@ -1887,7 +1889,7 @@ acl_app() {
;;
dot)
if [ "$(chinadns-ng -V | grep -i wolfssl)" != "nil" ]; then
- _chinadns_local_dns=$(config_t_get global direct_dns_dot "tls://1.12.12.12")
+ _chinadns_local_dns=$(config_t_get global direct_dns_dot "tls://dot.pub@1.12.12.12")
fi
;;
esac
diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/helper_chinadns_add.lua b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/helper_chinadns_add.lua
index 58a1cc5750..b4d2d1ca84 100644
--- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/helper_chinadns_add.lua
+++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/helper_chinadns_add.lua
@@ -502,6 +502,11 @@ end
table.insert(config_lines, "hosts")
+local cert_verify = uci:get(appname, "@global[0]", "chinadns_ng_cert_verify") or 0
+if tonumber(cert_verify) == 1 then
+ table.insert(config_lines, "cert-verify")
+end
+
if DEFAULT_TAG == "chn" then
log(string.format(" - 默认 DNS :%s", DNS_LOCAL))
elseif DEFAULT_TAG == "gfw" then
diff --git a/shadowsocks-rust/Cargo.lock b/shadowsocks-rust/Cargo.lock
index a56fed26d4..339fc978b4 100644
--- a/shadowsocks-rust/Cargo.lock
+++ b/shadowsocks-rust/Cargo.lock
@@ -421,7 +421,7 @@ dependencies = [
"indexmap",
"js-sys",
"once_cell",
- "rand 0.9.0",
+ "rand 0.9.1",
"serde",
"serde_bytes",
"serde_json",
@@ -1444,7 +1444,7 @@ dependencies = [
"once_cell",
"pin-project-lite",
"quinn",
- "rand 0.9.0",
+ "rand 0.9.1",
"ring",
"rustls",
"serde",
@@ -1471,7 +1471,7 @@ dependencies = [
"once_cell",
"parking_lot",
"quinn",
- "rand 0.9.0",
+ "rand 0.9.1",
"resolv-conf",
"rustls",
"serde",
@@ -2741,7 +2741,7 @@ checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc"
dependencies = [
"bytes",
"getrandom 0.3.2",
- "rand 0.9.0",
+ "rand 0.9.1",
"ring",
"rustc-hash 2.1.1",
"rustls",
@@ -2801,13 +2801,12 @@ dependencies = [
[[package]]
name = "rand"
-version = "0.9.0"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
+checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
- "zerocopy 0.8.24",
]
[[package]]
@@ -3381,7 +3380,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project",
- "rand 0.9.0",
+ "rand 0.9.1",
"sendfd",
"serde",
"serde_json",
@@ -3418,7 +3417,7 @@ dependencies = [
"ghash",
"hkdf",
"md-5",
- "rand 0.9.0",
+ "rand 0.9.1",
"ring-compat",
"sha1",
"sm4",
@@ -3448,7 +3447,7 @@ dependencies = [
"mimalloc",
"mime",
"qrcode",
- "rand 0.9.0",
+ "rand 0.9.1",
"reqwest",
"rpassword",
"rpmalloc",
@@ -3498,7 +3497,7 @@ dependencies = [
"nix",
"once_cell",
"pin-project",
- "rand 0.9.0",
+ "rand 0.9.1",
"regex",
"rocksdb",
"rustls-native-certs",
diff --git a/small/luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js b/small/luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js
index 0af6c5c146..f1bbaf7d49 100644
--- a/small/luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js
+++ b/small/luci-app-nikki/htdocs/luci-static/resources/tools/nikki.js
@@ -28,6 +28,7 @@ const callNikkiVersion = rpc.declare({
const callNikkiProfile = rpc.declare({
object: 'luci.nikki',
method: 'profile',
+ params: [ 'defaults' ],
expect: { '': {} }
});
@@ -98,8 +99,8 @@ return baseclass.extend({
return callNikkiVersion();
},
- profile: function () {
- return callNikkiProfile();
+ profile: function (defaults) {
+ return callNikkiProfile(defaults);
},
updateSubscription: function (section_id) {
@@ -107,7 +108,7 @@ return baseclass.extend({
},
api: async function (method, path, query, body) {
- const profile = await callNikkiProfile();
+ const profile = await callNikkiProfile({ 'external-controller': null, 'secret': null });
const apiListen = profile['external-controller'];
const apiSecret = profile['secret'] ?? '';
const apiPort = apiListen.substring(apiListen.lastIndexOf(':') + 1);
@@ -121,7 +122,7 @@ return baseclass.extend({
},
openDashboard: async function () {
- const profile = await callNikkiProfile();
+ const profile = await callNikkiProfile({ 'external-ui-name': null, 'external-controller': null, 'secret': null });
const uiName = profile['external-ui-name'];
const apiListen = profile['external-controller'];
const apiSecret = profile['secret'] ?? '';
diff --git a/small/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js b/small/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js
index f767309b2e..dbbff4e584 100644
--- a/small/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js
+++ b/small/luci-app-nikki/htdocs/luci-static/resources/view/nikki/proxy.js
@@ -67,6 +67,8 @@ return view.extend({
o.rmempty = false;
o = s.taboption('router', form.SectionValue, '_router_access_control', form.TableSection, 'router_access_control', _('Access Control'));
+ o.retain = true;
+ o.depends('router_proxy', '1');
o.subsection.addremove = true;
o.subsection.anonymous = true;
@@ -102,7 +104,9 @@ return view.extend({
o = s.taboption('lan', form.Flag, 'lan_proxy', _('Enable'));
o = s.taboption('lan', form.DynamicList, 'lan_inbound_interface', _('Inbound Interface'));
+ o.retain = true;
o.rmempty = false;
+ o.depends('lan_proxy', '1');
for (const network of networks) {
if (network.getName() === 'loopback') {
@@ -112,6 +116,8 @@ return view.extend({
}
o = s.taboption('lan', form.SectionValue, '_lan_access_control', form.TableSection, 'lan_access_control', _('Access Control'));
+ o.retain = true;
+ o.depends('lan_proxy', '1');
o.subsection.addremove = true;
o.subsection.anonymous = true;
diff --git a/small/luci-app-nikki/root/usr/share/rpcd/ucode/luci.nikki b/small/luci-app-nikki/root/usr/share/rpcd/ucode/luci.nikki
index 779989eaaf..2845020366 100644
--- a/small/luci-app-nikki/root/usr/share/rpcd/ucode/luci.nikki
+++ b/small/luci-app-nikki/root/usr/share/rpcd/ucode/luci.nikki
@@ -2,7 +2,7 @@
'use strict';
-import { access, popen } from 'fs';
+import { access, popen, writefile } from 'fs';
import { get_users, get_groups, get_cgroups } from '/etc/nikki/ucode/include.uc';
const methods = {
@@ -33,13 +33,18 @@ const methods = {
}
},
profile: {
- call: function() {
+ args: { defaults: {} },
+ call: function(req) {
let profile = {};
+ const defaults = req.args?.defaults ?? {};
const filepath = '/etc/nikki/run/config.yaml';
+ const tmpFilepath = '/var/run/nikki/profile.json';
if (access(filepath, 'r')) {
- const process = popen(`yq -p yaml -o json < ${filepath}`);
+ writefile(tmpFilepath, defaults);
+ const command = `yq -p yaml -o json eval-all 'select(fi == 0) *? select(fi == 1)' ${tmpFilepath} ${filepath}`;
+ const process = popen(command);
if (process) {
- profile = json(trim(process.read('all')));
+ profile = json(process);
process.close();
}
}
@@ -67,7 +72,7 @@ const methods = {
},
debug: {
call: function() {
- const success = system('/etc/nikki/scripts/debug.sh > /var/log/nikki/debug.log 2>&1') == 0;
+ const success = system('/etc/nikki/scripts/debug.sh > /var/log/nikki/debug.log') == 0;
return { success: success };
}
}
diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
index 425cd70d5d..9672c449e4 100644
--- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
+++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
@@ -365,15 +365,15 @@ o:value("119.28.28.28")
o:depends("direct_dns_mode", "tcp")
o = s:taboption("DNS", Value, "direct_dns_dot", translate("Direct DNS DoT"))
-o.default = "tls://1.12.12.12"
-o:value("tls://1.12.12.12")
-o:value("tls://120.53.53.53")
-o:value("tls://36.99.170.86")
-o:value("tls://101.198.191.4")
-o:value("tls://223.5.5.5")
-o:value("tls://223.6.6.6")
-o:value("tls://2400:3200::1")
-o:value("tls://2400:3200:baba::1")
+o.default = "tls://dot.pub@1.12.12.12"
+o:value("tls://dot.pub@1.12.12.12")
+o:value("tls://dot.pub@120.53.53.53")
+o:value("tls://dot.360.cn@36.99.170.86")
+o:value("tls://dot.360.cn@101.198.191.4")
+o:value("tls://dns.alidns.com@223.5.5.5")
+o:value("tls://dns.alidns.com@223.6.6.6")
+o:value("tls://dns.alidns.com@2400:3200::1")
+o:value("tls://dns.alidns.com@2400:3200:baba::1")
o.validate = chinadns_dot_validate
o:depends("direct_dns_mode", "dot")
@@ -515,17 +515,17 @@ o:depends({singbox_dns_mode = "tcp"})
---- DoT
o = s:taboption("DNS", Value, "remote_dns_dot", translate("Remote DNS DoT"))
-o.default = "tls://1.1.1.1"
-o:value("tls://1.0.0.1", "1.0.0.1 (CloudFlare)")
-o:value("tls://1.1.1.1", "1.1.1.1 (CloudFlare)")
-o:value("tls://8.8.4.4", "8.8.4.4 (Google)")
-o:value("tls://8.8.8.8", "8.8.8.8 (Google)")
-o:value("tls://9.9.9.9", "9.9.9.9 (Quad9)")
-o:value("tls://149.112.112.112", "149.112.112.112 (Quad9)")
-o:value("tls://94.140.14.14", "94.140.14.14 (AdGuard)")
-o:value("tls://94.140.15.15", "94.140.15.15 (AdGuard)")
-o:value("tls://208.67.222.222", "208.67.222.222 (OpenDNS)")
-o:value("tls://208.67.220.220", "208.67.220.220 (OpenDNS)")
+o.default = "tls://one.one.one.one@1.1.1.1"
+o:value("tls://one.one.one.one@1.0.0.1", "1.0.0.1 (CloudFlare)")
+o:value("tls://one.one.one.one@1.1.1.1", "1.1.1.1 (CloudFlare)")
+o:value("tls://dns.google@8.8.4.4", "8.8.4.4 (Google)")
+o:value("tls://dns.google@8.8.8.8", "8.8.8.8 (Google)")
+o:value("tls://dns.quad9.net@9.9.9.9", "9.9.9.9 (Quad9)")
+o:value("tls://dns.quad9.net@149.112.112.112", "149.112.112.112 (Quad9)")
+o:value("tls://dns.adguard.com@94.140.14.14", "94.140.14.14 (AdGuard)")
+o:value("tls://dns.adguard.com@94.140.15.15", "94.140.15.15 (AdGuard)")
+o:value("tls://dns.opendns.com@208.67.222.222", "208.67.222.222 (OpenDNS)")
+o:value("tls://dns.opendns.com@208.67.220.220", "208.67.220.220 (OpenDNS)")
o.validate = chinadns_dot_validate
o:depends("dns_mode", "dot")
@@ -604,6 +604,11 @@ if api.is_finded("smartdns") then
o:depends({dns_shunt = "smartdns", tcp_proxy_mode = "proxy", chn_list = "direct"})
end
+o = s:taboption("DNS", Flag, "chinadns_ng_cert_verify", translate("DoT Cert verify"), translate("Verify DoT SSL cert. (May fail on some platforms!)"))
+o.default = "0"
+o:depends({direct_dns_mode = "dot"})
+o:depends({dns_mode = "dot"})
+
o = s:taboption("DNS", Flag, "dns_redirect", translate("DNS Redirect"), translate("Force special DNS server to need proxy devices."))
o.default = "1"
o.rmempty = false
diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
index 61dd830f99..208f7e3621 100644
--- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
+++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
@@ -27,7 +27,7 @@ local show_node_info = m:get("@global_other[0]", "show_node_info") or "0"
if auto_detection_time ~= "0" then
local node_count = 0
for k, e in ipairs(api.get_valid_nodes()) do
- if e.protocol ~= "_shunt" and e.protocol ~= "_balancing" and e.protocol ~= "_urltest" and e.protocol ~= "_iface" then
+ if e.node_type == "normal" then
node_count = node_count + 1
end
end
@@ -168,8 +168,10 @@ o = s:option(DummyValue, "ping", "Ping")
o.width = "8%"
o.rawhtml = true
o.cfgvalue = function(t, n)
- local protocol = m:get(n, "protocol")
- if protocol == "_shunt" or protocol == "_balancing" or protocol == "_urltest" or protocol == "_iface" then
+ local type = m:get(n, "type") or ""
+ local protocol = m:get(n, "protocol") or ""
+ if (type == "sing-box" or type == "Xray") and
+ (protocol == "_shunt" or protocol == "_balancing" or protocol == "_urltest" or protocol == "_iface") then
return string.format('---', n)
end
if auto_detection_time ~= "icmp" then
@@ -184,8 +186,10 @@ o = s:option(DummyValue, "tcping", "TCPing")
o.width = "8%"
o.rawhtml = true
o.cfgvalue = function(t, n)
- local protocol = m:get(n, "protocol")
- if protocol == "_shunt" or protocol == "_balancing" or protocol == "_urltest" or protocol == "_iface" then
+ local type = m:get(n, "type") or ""
+ local protocol = m:get(n, "protocol") or ""
+ if (type == "sing-box" or type == "Xray") and
+ (protocol == "_shunt" or protocol == "_balancing" or protocol == "_urltest" or protocol == "_iface") then
return string.format('---', n)
end
if auto_detection_time ~= "tcping" then
diff --git a/small/luci-app-passwall/po/zh-cn/passwall.po b/small/luci-app-passwall/po/zh-cn/passwall.po
index a93efa4449..c28a4e6cad 100644
--- a/small/luci-app-passwall/po/zh-cn/passwall.po
+++ b/small/luci-app-passwall/po/zh-cn/passwall.po
@@ -232,6 +232,12 @@ msgstr "清空 IPSET"
msgid "Clear NFTSET"
msgstr "清空 NFTSET"
+msgid "DoT Cert verify"
+msgstr "DoT 证书验证"
+
+msgid "Verify DoT SSL cert. (May fail on some platforms!)"
+msgstr "验证 DoT SSL 证书。(在某些平台可能无法验证,谨慎开启!)"
+
msgid "Try this feature if the rule modification does not take effect."
msgstr "如果修改规则后没有生效,请尝试此功能。"
diff --git a/small/luci-app-passwall/root/usr/share/passwall/app.sh b/small/luci-app-passwall/root/usr/share/passwall/app.sh
index 0c5321d245..50a8dec291 100755
--- a/small/luci-app-passwall/root/usr/share/passwall/app.sh
+++ b/small/luci-app-passwall/root/usr/share/passwall/app.sh
@@ -933,7 +933,7 @@ run_redir() {
_args="${_args} direct_dns_tcp_server=$(config_t_get global direct_dns_tcp 223.5.5.5 | sed 's/:/#/g')"
;;
dot)
- local tmp_dot_dns=$(config_t_get global direct_dns_dot "tls://1.12.12.12")
+ local tmp_dot_dns=$(config_t_get global direct_dns_dot "tls://dot.pub@1.12.12.12")
local tmp_dot_ip=$(echo "$tmp_dot_dns" | sed -n 's/.*:\/\/\([^@#]*@\)*\([^@#]*\).*/\2/p')
local tmp_dot_port=$(echo "$tmp_dot_dns" | sed -n 's/.*#\([0-9]\+\).*/\1/p')
_args="${_args} direct_dns_dot_server=$tmp_dot_ip#${tmp_dot_port:-853}"
@@ -1420,13 +1420,14 @@ start_dns() {
;;
dot)
if [ "$chinadns_tls" != "nil" ]; then
- local DIRECT_DNS=$(config_t_get global direct_dns_dot "tls://1.12.12.12")
+ local DIRECT_DNS=$(config_t_get global direct_dns_dot "tls://dot.pub@1.12.12.12")
+ local cert_verify=$([ "$(config_t_get global chinadns_ng_cert_verify 0)" = "1" ] && echo "--cert-verify")
china_ng_local_dns=${DIRECT_DNS}
#当全局(包括访问控制节点)开启chinadns-ng时,不启动新进程。
[ "$DNS_SHUNT" != "chinadns-ng" ] || [ "$ACL_RULE_DNSMASQ" = "1" ] && {
LOCAL_DNS="127.0.0.1#${NEXT_DNS_LISTEN_PORT}"
- ln_run "$(first_type chinadns-ng)" chinadns-ng "/dev/null" -b 127.0.0.1 -l ${NEXT_DNS_LISTEN_PORT} -c ${DIRECT_DNS} -d chn
+ ln_run "$(first_type chinadns-ng)" chinadns-ng "/dev/null" -b 127.0.0.1 -l ${NEXT_DNS_LISTEN_PORT} -c ${DIRECT_DNS} -d chn ${cert_verify}
echolog " - ChinaDNS-NG(${LOCAL_DNS}) -> ${DIRECT_DNS}"
echolog " * 请确保上游直连 DNS 支持 DoT 查询。"
NEXT_DNS_LISTEN_PORT=$(expr $NEXT_DNS_LISTEN_PORT + 1)
@@ -1542,13 +1543,14 @@ start_dns() {
TCP_PROXY_DNS=1
if [ "$chinadns_tls" != "nil" ]; then
local china_ng_listen_port=${NEXT_DNS_LISTEN_PORT}
- local china_ng_trust_dns=$(config_t_get global remote_dns_dot "tls://1.1.1.1")
+ local china_ng_trust_dns=$(config_t_get global remote_dns_dot "tls://one.one.one.one@1.1.1.1")
+ local cert_verify=$([ "$(config_t_get global chinadns_ng_cert_verify 0)" = "1" ] && echo "--cert-verify")
local tmp_dot_ip=$(echo "$china_ng_trust_dns" | sed -n 's/.*:\/\/\([^@#]*@\)*\([^@#]*\).*/\2/p')
local tmp_dot_port=$(echo "$china_ng_trust_dns" | sed -n 's/.*#\([0-9]\+\).*/\1/p')
REMOTE_DNS="$tmp_dot_ip#${tmp_dot_port:-853}"
[ "$DNS_SHUNT" != "chinadns-ng" ] && {
[ "$FILTER_PROXY_IPV6" = "1" ] && DNSMASQ_FILTER_PROXY_IPV6=0 && local no_ipv6_trust="-N"
- ln_run "$(first_type chinadns-ng)" chinadns-ng "/dev/null" -b 127.0.0.1 -l ${china_ng_listen_port} -t ${china_ng_trust_dns} -d gfw ${no_ipv6_trust}
+ ln_run "$(first_type chinadns-ng)" chinadns-ng "/dev/null" -b 127.0.0.1 -l ${china_ng_listen_port} -t ${china_ng_trust_dns} -d gfw ${no_ipv6_trust} ${cert_verify}
echolog " - ChinaDNS-NG(${TUN_DNS}) -> ${china_ng_trust_dns}"
}
else
@@ -1887,7 +1889,7 @@ acl_app() {
;;
dot)
if [ "$(chinadns-ng -V | grep -i wolfssl)" != "nil" ]; then
- _chinadns_local_dns=$(config_t_get global direct_dns_dot "tls://1.12.12.12")
+ _chinadns_local_dns=$(config_t_get global direct_dns_dot "tls://dot.pub@1.12.12.12")
fi
;;
esac
diff --git a/small/luci-app-passwall/root/usr/share/passwall/helper_chinadns_add.lua b/small/luci-app-passwall/root/usr/share/passwall/helper_chinadns_add.lua
index 58a1cc5750..b4d2d1ca84 100644
--- a/small/luci-app-passwall/root/usr/share/passwall/helper_chinadns_add.lua
+++ b/small/luci-app-passwall/root/usr/share/passwall/helper_chinadns_add.lua
@@ -502,6 +502,11 @@ end
table.insert(config_lines, "hosts")
+local cert_verify = uci:get(appname, "@global[0]", "chinadns_ng_cert_verify") or 0
+if tonumber(cert_verify) == 1 then
+ table.insert(config_lines, "cert-verify")
+end
+
if DEFAULT_TAG == "chn" then
log(string.format(" - 默认 DNS :%s", DNS_LOCAL))
elseif DEFAULT_TAG == "gfw" then
diff --git a/small/nikki/Makefile b/small/nikki/Makefile
index fbab23ae52..d184ecf24e 100644
--- a/small/nikki/Makefile
+++ b/small/nikki/Makefile
@@ -5,9 +5,9 @@ PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git
-PKG_SOURCE_DATE:=2025-04-17
-PKG_SOURCE_VERSION:=76052b5b26f532b916643edf0b24782fa3195fb2
-PKG_MIRROR_HASH:=346ada947d1408bb81faf19cada29d2a70d71f1accf967221c7e1b362d4fbcab
+PKG_SOURCE_DATE:=2025-04-18
+PKG_SOURCE_VERSION:=619c9dc0c633c12ac46e38b7b423e7c01f06197f
+PKG_MIRROR_HASH:=907be1b4f33e71bd9aa36f0cd76aefbce39a1b346f80ff09730c47fa80be4658
PKG_LICENSE:=GPL3.0+
PKG_MAINTAINER:=Joseph Mory
@@ -16,7 +16,7 @@ PKG_BUILD_DEPENDS:=golang/host
PKG_BUILD_PARALLEL:=1
PKG_BUILD_FLAGS:=no-mips16
-PKG_BUILD_VERSION:=alpha-76052b5
+PKG_BUILD_VERSION:=alpha-619c9dc
PKG_BUILD_TIME:=$(shell date -u -Iseconds)
GO_PKG:=github.com/metacubex/mihomo
diff --git a/small/nikki/files/scripts/debug.sh b/small/nikki/files/scripts/debug.sh
index 31444daa2c..6ca706eb84 100644
--- a/small/nikki/files/scripts/debug.sh
+++ b/small/nikki/files/scripts/debug.sh
@@ -42,9 +42,12 @@ fi
`
ucode -S -e '
import { cursor } from "uci";
+
const uci = cursor();
+
const config = uci.get_all("nikki");
const result = {};
+
for (let section_id in config) {
const section = config[section_id];
const section_type = section[".type"];
@@ -61,13 +64,45 @@ for (let section_type in result) {
delete section[".index"];
}
}
-for (let x in result["subscription"]) {
- x["url"] = "*";
+if (exists(result, "mixin")) {
+ for (let x in result["mixin"]) {
+ if (exists(x, "api_secret")) {
+ x["api_secret"] = "*";
+ }
+ }
}
-for (let x in result["lan_access_control"]) {
- x["ip"] = "*";
- x["ip6"] = "*";
- x["mac"] = "*";
+if (exists(result, "authentication")) {
+ for (let x in result["authentication"]) {
+ if (exists(x, "password")) {
+ x["password"] = "*";
+ }
+ }
+}
+if (exists(result, "subscription")) {
+ for (let x in result["subscription"]) {
+ if (exists(x, "url")) {
+ x["url"] = "*";
+ }
+ }
+}
+if (exists(result, "lan_access_control")) {
+ for (let x in result["lan_access_control"]) {
+ if (exists(x, "ip")) {
+ for (let i = 0; i < length(x["ip"]); i++) {
+ x["ip"][i] = "*";
+ }
+ }
+ if (exists(x, "ip6")) {
+ for (let i = 0; i < length(x["ip6"]); i++) {
+ x["ip6"][i] = "*";
+ }
+ }
+ if (exists(x, "mac")) {
+ for (let i = 0; i < length(x["mac"]); i++) {
+ x["mac"][i] = "*";
+ }
+ }
+ }
}
delete result["status"];
delete result["editor"];
@@ -77,53 +112,83 @@ print(result);
`
\`\`\`
## profile
-\`\`\`yaml
+\`\`\`json
`
-yq -M -P '
-. |= (
- select(has("secret")) | .secret = "*" |
- select(has("authentication")) | .authentication = []
-) |
-.proxy-providers.* |= (
- select(has("url")) |= .url = "*" |
- select(has("payload")) |= .payload[] |= (
- select(has("server")) |= .server = "*" |
- select(has("servername")) |= .servername = "*" |
- select(has("sni")) |= .sni = "*" |
- select(has("port")) |= .port = "*" |
- select(has("ports")) |= .ports = "*" |
- select(has("port-range")) |= .port-range = "*" |
- select(has("uuid")) |= .uuid = "*" |
- select(has("private-key")) |= .private-key = "*" |
- select(has("public-key")) |= .public-key = "*" |
- select(has("token")) |= .token="*" |
- select(has("username")) |= .username = "*" |
- select(has("password")) |= .password = "*" |
- select(has("peers")) |= .peers[] |= (
- select(has("server")) |= .server = "*" |
- select(has("public-key")) |= .public-key = "*"
- )
- )
-) |
-.proxies[] |= (
- select(has("server")) |= .server = "*" |
- select(has("servername")) |= .servername = "*" |
- select(has("sni")) |= .sni = "*" |
- select(has("port")) |= .port = "*" |
- select(has("ports")) |= .ports = "*" |
- select(has("port-range")) |= .port-range = "*" |
- select(has("uuid")) |= .uuid = "*" |
- select(has("private-key")) |= .private-key = "*" |
- select(has("public-key")) |= .public-key = "*" |
- select(has("token")) |= .token="*" |
- select(has("username")) |= .username = "*" |
- select(has("password")) |= .password = "*" |
- select(has("peers")) |= .peers[] |= (
- select(has("server")) |= .server = "*" |
- select(has("public-key")) |= .public-key = "*"
- )
-)
-' < /etc/nikki/run/config.yaml
+ucode -S -e '
+import { popen } from "fs";
+
+function desensitize_proxies(proxies) {
+ for (let x in proxies) {
+ if (exists(x, "server")) {
+ x["server"] = "*";
+ }
+ if (exists(x, "servername")) {
+ x["servername"] = "*";
+ }
+ if (exists(x, "sni")) {
+ x["sni"] = "*";
+ }
+ if (exists(x, "port")) {
+ x["port"] = "*";
+ }
+ if (exists(x, "ports")) {
+ x["ports"] = "*";
+ }
+ if (exists(x, "port-range")) {
+ x["port-range"] = "*";
+ }
+ if (exists(x, "uuid")) {
+ x["uuid"] = "*";
+ }
+ if (exists(x, "private-key")) {
+ x["private-key"] = "*";
+ }
+ if (exists(x, "public-key")) {
+ x["public-key"] = "*";
+ }
+ if (exists(x, "token")) {
+ x["token"] = "*";
+ }
+ if (exists(x, "username")) {
+ x["username"] = "*";
+ }
+ if (exists(x, "password")) {
+ x["password"] = "*";
+ }
+ }
+}
+
+function desensitize_profile() {
+ let profile = {};
+ const process = popen("yq -p yaml -o json /etc/nikki/run/config.yaml");
+ if (process) {
+ profile = json(process);
+ if (exists(profile, "secret")) {
+ profile["secret"] = "*";
+ }
+ if (exists(profile, "authentication")) {
+ profile["authentication"] = [];
+ }
+ if (exists(profile, "proxy-providers")) {
+ for (let x in profile["proxy-providers"]) {
+ if (exists(x, "url")) {
+ x["url"] = "*";
+ }
+ if (exists(x, "payload")) {
+ desensitize_proxies(x["payload"]);
+ }
+ }
+ }
+ if (exists(profile, "proxies")) {
+ desensitize_proxies(profile["proxies"]);
+ }
+ process.close();
+ }
+ return profile;
+}
+
+print(desensitize_profile());
+'
`
\`\`\`
## ip rule
diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs
index ec07b82562..5edab5d4d2 100644
--- a/v2rayn/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs
+++ b/v2rayn/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs
@@ -548,8 +548,11 @@ public class MainWindowViewModel : MyReactiveObject
BlReloadEnabled = false;
- await LoadCore();
- await SysProxyHandler.UpdateSysProxy(_config, false);
+ await Task.Run(async () =>
+ {
+ await LoadCore();
+ await SysProxyHandler.UpdateSysProxy(_config, false);
+ });
Locator.Current.GetService()?.TestServerAvailability();
_updateView?.Invoke(EViewAction.DispatcherReload, null);
diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs
index 4c7a5c1cc4..00cc391a3f 100644
--- a/v2rayn/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs
+++ b/v2rayn/v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs
@@ -318,7 +318,10 @@ public class StatusBarViewModel : MyReactiveObject
_updateView?.Invoke(EViewAction.DispatcherServerAvailability, ResUI.Speedtesting);
- var msg = await (new UpdateService()).RunAvailabilityCheck();
+ var msg = await Task.Run(async () =>
+ {
+ return await (new UpdateService()).RunAvailabilityCheck();
+ });
NoticeHandler.Instance.SendMessageEx(msg);
_updateView?.Invoke(EViewAction.DispatcherServerAvailability, msg);
diff --git a/v2rayng/AndroidLibXrayLite/.gitignore b/v2rayng/AndroidLibXrayLite/.gitignore
index 3cd9b32059..30a9b9c98b 100644
--- a/v2rayng/AndroidLibXrayLite/.gitignore
+++ b/v2rayng/AndroidLibXrayLite/.gitignore
@@ -1,4 +1,5 @@
# Ignore editor directories and files
+.idea/
.vscode/
# Ignore Gradle files
diff --git a/v2rayng/AndroidLibXrayLite/libv2ray_main.go b/v2rayng/AndroidLibXrayLite/libv2ray_main.go
index 3a56f413e0..99de802e04 100644
--- a/v2rayng/AndroidLibXrayLite/libv2ray_main.go
+++ b/v2rayng/AndroidLibXrayLite/libv2ray_main.go
@@ -37,7 +37,7 @@ type CoreController struct {
CallbackHandler CoreCallbackHandler
statsManager corestats.Manager
coreMutex sync.Mutex
- CoreInstance *core.Instance
+ coreInstance *core.Instance
IsRunning bool
}
@@ -45,7 +45,6 @@ type CoreController struct {
type CoreCallbackHandler interface {
Startup() int
Shutdown() int
- Protect(int) bool
OnEmitStatus(int, string) int
}
@@ -142,7 +141,7 @@ func (x *CoreController) MeasureDelay(url string) (int64, error) {
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
defer cancel()
- return measureInstDelay(ctx, x.CoreInstance, url)
+ return measureInstDelay(ctx, x.coreInstance, url)
}
// MeasureOutboundDelay measures the outbound delay for a given configuration and URL
@@ -179,9 +178,9 @@ func CheckVersionX() string {
// doShutdown shuts down the Xray instance and cleans up resources
func (x *CoreController) doShutdown() {
- if x.CoreInstance != nil {
- x.CoreInstance.Close()
- x.CoreInstance = nil
+ if x.coreInstance != nil {
+ x.coreInstance.Close()
+ x.coreInstance = nil
}
x.IsRunning = false
x.statsManager = nil
@@ -196,15 +195,15 @@ func (x *CoreController) doStartLoop(configContent string) error {
}
log.Println("Creating new core instance")
- x.CoreInstance, err = core.New(config)
+ x.coreInstance, err = core.New(config)
if err != nil {
return fmt.Errorf("failed to create core instance: %w", err)
}
- x.statsManager = x.CoreInstance.GetFeature(corestats.ManagerType()).(corestats.Manager)
+ x.statsManager = x.coreInstance.GetFeature(corestats.ManagerType()).(corestats.Manager)
log.Println("Starting core")
x.IsRunning = true
- if err := x.CoreInstance.Start(); err != nil {
+ if err := x.coreInstance.Start(); err != nil {
x.IsRunning = false
return fmt.Errorf("failed to start core: %w", err)
}
diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ProfileLiteItem.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ProfileLiteItem.kt
deleted file mode 100644
index 12995abd76..0000000000
--- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ProfileLiteItem.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.v2ray.ang.dto
-
-data class ProfileLiteItem(
- val configType: EConfigType,
- var subscriptionId: String = "",
- var remarks: String = "",
- var server: String?,
- var serverPort: Int?,
-)
\ No newline at end of file
diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/V2rayConfig.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/V2rayConfig.kt
index 00de185d8f..f4dedf2278 100644
--- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/V2rayConfig.kt
+++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/V2rayConfig.kt
@@ -1,25 +1,18 @@
package com.v2ray.ang.dto
-import android.text.TextUtils
-import com.google.gson.GsonBuilder
-import com.google.gson.JsonPrimitive
-import com.google.gson.JsonSerializationContext
-import com.google.gson.JsonSerializer
import com.google.gson.annotations.SerializedName
-import com.google.gson.reflect.TypeToken
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.OutSettingsBean.ServersBean
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.OutSettingsBean.VnextBean
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.UsersBean
import com.v2ray.ang.dto.V2rayConfig.OutboundBean.OutSettingsBean.WireGuardBean
import com.v2ray.ang.util.Utils
-import java.lang.reflect.Type
data class V2rayConfig(
var remarks: String? = null,
var stats: Any? = null,
val log: LogBean,
- var policy: PolicyBean?,
+ var policy: PolicyBean? = null,
val inbounds: ArrayList,
var outbounds: ArrayList,
var dns: DnsBean? = null,
@@ -36,7 +29,7 @@ data class V2rayConfig(
data class LogBean(
val access: String,
val error: String,
- var loglevel: String?,
+ var loglevel: String? = null,
val dnsLog: Boolean? = null
)
@@ -46,7 +39,7 @@ data class V2rayConfig(
var protocol: String,
var listen: String? = null,
val settings: Any? = null,
- val sniffing: SniffingBean?,
+ val sniffing: SniffingBean? = null,
val streamSettings: Any? = null,
val allocate: Any? = null
) {
@@ -299,7 +292,8 @@ data class V2rayConfig(
var tcpFastOpen: Boolean? = null,
var tproxy: String? = null,
var mark: Int? = null,
- var dialerProxy: String? = null
+ var dialerProxy: String? = null,
+ var domainStrategy: String? = null
)
data class TlsSettingsBean(
@@ -514,6 +508,18 @@ data class V2rayConfig(
}
return null
}
+
+ fun ensureSockopt(): V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean {
+ val stream = streamSettings ?: V2rayConfig.OutboundBean.StreamSettingsBean().also {
+ streamSettings = it
+ }
+
+ val sockopt = stream.sockopt ?: V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean().also {
+ stream.sockopt = it
+ }
+
+ return sockopt
+ }
}
data class DnsBean(
@@ -590,4 +596,9 @@ data class V2rayConfig(
return null
}
+ fun getAllProxyOutbound(): List {
+ return outbounds.filter { outbound ->
+ EConfigType.entries.any { it.name.equals(outbound.protocol, ignoreCase = true) }
+ }
+ }
}
\ No newline at end of file
diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt
index bc3229fe45..9cea554419 100644
--- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt
+++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/FmtBase.kt
@@ -302,8 +302,4 @@ open class FmtBase {
}
}
- fun resolveHostToIP(server: String?): String {
- return HttpUtil.resolveHostToIP(server.orEmpty(), MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true)
- }
-
}
diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/HttpFmt.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/HttpFmt.kt
index 2ff28d8c18..faac023083 100644
--- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/HttpFmt.kt
+++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/HttpFmt.kt
@@ -16,7 +16,7 @@ object HttpFmt : FmtBase() {
val outboundBean = OutboundBean.create(EConfigType.HTTP)
outboundBean?.settings?.servers?.first()?.let { server ->
- server.address = resolveHostToIP(profileItem.server)
+ server.address = profileItem.server.orEmpty()
server.port = profileItem.serverPort.orEmpty().toInt()
if (profileItem.username.isNotNullEmpty()) {
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/ShadowsocksFmt.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/ShadowsocksFmt.kt
index d0acc28d93..e3c0edc564 100644
--- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/ShadowsocksFmt.kt
+++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/ShadowsocksFmt.kt
@@ -134,7 +134,7 @@ object ShadowsocksFmt : FmtBase() {
val outboundBean = OutboundBean.create(EConfigType.SHADOWSOCKS)
outboundBean?.settings?.servers?.first()?.let { server ->
- server.address = resolveHostToIP(profileItem.server)
+ server.address = profileItem.server.orEmpty()
server.port = profileItem.serverPort.orEmpty().toInt()
server.password = profileItem.password
server.method = profileItem.method
diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/SocksFmt.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/SocksFmt.kt
index fad0bb4ca7..f37030c224 100644
--- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/SocksFmt.kt
+++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/SocksFmt.kt
@@ -63,7 +63,7 @@ object SocksFmt : FmtBase() {
val outboundBean = OutboundBean.create(EConfigType.SOCKS)
outboundBean?.settings?.servers?.first()?.let { server ->
- server.address = resolveHostToIP(profileItem.server)
+ server.address = profileItem.server.orEmpty()
server.port = profileItem.serverPort.orEmpty().toInt()
if (profileItem.username.isNotNullEmpty()) {
val socksUsersBean = OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/TrojanFmt.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/TrojanFmt.kt
index 48dd8f4ee2..f9c06299c3 100644
--- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/TrojanFmt.kt
+++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/TrojanFmt.kt
@@ -63,7 +63,7 @@ object TrojanFmt : FmtBase() {
val outboundBean = OutboundBean.create(EConfigType.TROJAN)
outboundBean?.settings?.servers?.first()?.let { server ->
- server.address = resolveHostToIP(profileItem.server)
+ server.address = profileItem.server.orEmpty()
server.port = profileItem.serverPort.orEmpty().toInt()
server.password = profileItem.password
server.flow = profileItem.flow
diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/VlessFmt.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/VlessFmt.kt
index 1438cd7e76..ffa8f28e57 100644
--- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/VlessFmt.kt
+++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/VlessFmt.kt
@@ -59,7 +59,7 @@ object VlessFmt : FmtBase() {
val outboundBean = OutboundBean.create(EConfigType.VLESS)
outboundBean?.settings?.vnext?.first()?.let { vnext ->
- vnext.address = resolveHostToIP(profileItem.server)
+ vnext.address = profileItem.server.orEmpty()
vnext.port = profileItem.serverPort.orEmpty().toInt()
vnext.users[0].id = profileItem.password.orEmpty()
vnext.users[0].encryption = profileItem.method
diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/VmessFmt.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/VmessFmt.kt
index cb157e1e98..3e56b84e3d 100644
--- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/VmessFmt.kt
+++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/VmessFmt.kt
@@ -171,7 +171,7 @@ object VmessFmt : FmtBase() {
val outboundBean = OutboundBean.create(EConfigType.VMESS)
outboundBean?.settings?.vnext?.first()?.let { vnext ->
- vnext.address = resolveHostToIP(profileItem.server)
+ vnext.address = profileItem.server.orEmpty()
vnext.port = profileItem.serverPort.orEmpty().toInt()
vnext.users[0].id = profileItem.password.orEmpty()
vnext.users[0].security = profileItem.method
diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/WireguardFmt.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/WireguardFmt.kt
index 44ce1a0471..e2bc5f9039 100644
--- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/WireguardFmt.kt
+++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/fmt/WireguardFmt.kt
@@ -113,7 +113,7 @@ object WireguardFmt : FmtBase() {
wireguard.peers?.firstOrNull()?.let { peer ->
peer.publicKey = profileItem.publicKey.orEmpty()
peer.preSharedKey = profileItem.preSharedKey?.takeIf { it.isNotEmpty() }
- peer.endpoint = Utils.getIpv6Address(resolveHostToIP(profileItem.server)) + ":${profileItem.serverPort}"
+ peer.endpoint = Utils.getIpv6Address(profileItem.server) + ":${profileItem.serverPort}"
}
wireguard.mtu = profileItem.mtu
wireguard.reserved = profileItem.reserved?.takeIf { it.isNotBlank() }?.split(",")?.filter { it.isNotBlank() }?.map { it.trim().toInt() }
diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt
index 51c6652309..42ac0a3eac 100644
--- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt
+++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/handler/V2rayConfigManager.kt
@@ -46,6 +46,7 @@ import com.v2ray.ang.fmt.TrojanFmt
import com.v2ray.ang.fmt.VlessFmt
import com.v2ray.ang.fmt.VmessFmt
import com.v2ray.ang.fmt.WireguardFmt
+import com.v2ray.ang.util.HttpUtil
import com.v2ray.ang.util.JsonUtil
import com.v2ray.ang.util.Utils
@@ -150,6 +151,8 @@ object V2rayConfigManager {
v2rayConfig.policy = null
}
+ resolveProxyDomainsToHosts(v2rayConfig)
+
result.status = true
result.content = JsonUtil.toJsonPretty(v2rayConfig) ?: ""
result.domainPort = if (retMore.first) retMore.second else retOut.second
@@ -699,13 +702,7 @@ object V2rayConfigManager {
updateOutboundWithGlobalSettings(prevOutbound)
prevOutbound.tag = TAG_PROXY + "2"
v2rayConfig.outbounds.add(prevOutbound)
- if (outbound.streamSettings == null) {
- outbound.streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()
- }
- outbound.streamSettings?.sockopt =
- V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean(
- dialerProxy = prevOutbound.tag
- )
+ outbound.ensureSockopt().dialerProxy = prevOutbound.tag
domainPort = prevNode.getServerAddressAndPort()
}
}
@@ -719,13 +716,7 @@ object V2rayConfigManager {
nextOutbound.tag = TAG_PROXY
v2rayConfig.outbounds.add(0, nextOutbound)
outbound.tag = TAG_PROXY + "1"
- if (nextOutbound.streamSettings == null) {
- nextOutbound.streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()
- }
- nextOutbound.streamSettings?.sockopt =
- V2rayConfig.OutboundBean.StreamSettingsBean.SockoptBean(
- dialerProxy = outbound.tag
- )
+ nextOutbound.ensureSockopt().dialerProxy = outbound.tag
if (nextNode.configType == EConfigType.WIREGUARD) {
domainPort = nextNode.getServerAddressAndPort()
}
@@ -763,4 +754,37 @@ object V2rayConfigManager {
}
+ private fun resolveProxyDomainsToHosts(v2rayConfig: V2rayConfig) {
+ val proxyOutboundList = v2rayConfig.getAllProxyOutbound()
+ val dns = v2rayConfig.dns ?: return
+
+ val newHosts = dns.hosts?.toMutableMap() ?: mutableMapOf()
+
+ val preferIpv6 = MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6) == true
+
+ for (item in proxyOutboundList) {
+ val domain = item.getServerAddress()
+ if (domain.isNullOrEmpty()) continue
+
+ if (newHosts.containsKey(domain)) continue
+
+ val resolvedIps = HttpUtil.resolveHostToIP(
+ domain,
+ preferIpv6
+ )
+
+ if (resolvedIps.isEmpty()) continue
+
+ item.ensureSockopt().domainStrategy =
+ if (preferIpv6) "UseIPv6v4" else "UseIPv4v6"
+
+ newHosts[domain] = if (resolvedIps.size == 1) {
+ resolvedIps[0]
+ } else {
+ resolvedIps
+ }
+ }
+
+ dns.hosts = newHosts
+ }
}
diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayServiceManager.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayServiceManager.kt
index de02ac6e28..8e2c85c620 100644
--- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayServiceManager.kt
+++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayServiceManager.kt
@@ -95,7 +95,9 @@ object V2RayServiceManager {
* @param context The context from which the service is started.
*/
private fun startContextService(context: Context) {
- if (coreController.isRunning) return
+ if (coreController.isRunning) {
+ return
+ }
val guid = MmkvManager.getSelectServer() ?: return
val config = MmkvManager.decodeServerConfig(guid) ?: return
if (config.configType != EConfigType.CUSTOM
@@ -128,12 +130,13 @@ object V2RayServiceManager {
* Starts the V2Ray core service.
*/
fun startCoreLoop(): Boolean {
- val service = getService() ?: return false
- val guid = MmkvManager.getSelectServer() ?: return false
- val config = MmkvManager.decodeServerConfig(guid) ?: return false
if (coreController.isRunning) {
return false
}
+
+ val service = getService() ?: return false
+ val guid = MmkvManager.getSelectServer() ?: return false
+ val config = MmkvManager.decodeServerConfig(guid) ?: return false
val result = V2rayConfigManager.getV2rayConfig(service, guid)
if (!result.status)
return false
@@ -224,30 +227,34 @@ object V2RayServiceManager {
* Also fetches remote IP information if the delay test was successful.
*/
private fun measureV2rayDelay() {
+ if (coreController.isRunning == false) {
+ return
+ }
+
CoroutineScope(Dispatchers.IO).launch {
val service = getService() ?: return@launch
var time = -1L
- var errstr = ""
- if (coreController.isRunning) {
+ var errorStr = ""
+
+ try {
+ time = coreController.measureDelay(SettingsManager.getDelayTestUrl())
+ } catch (e: Exception) {
+ Log.e(AppConfig.TAG, "Failed to measure delay with primary URL", e)
+ errorStr = e.message?.substringAfter("\":") ?: "empty message"
+ }
+ if (time == -1L) {
try {
- time = coreController.measureDelay(SettingsManager.getDelayTestUrl())
+ time = coreController.measureDelay(SettingsManager.getDelayTestUrl(true))
} catch (e: Exception) {
- Log.e(AppConfig.TAG, "Failed to measure delay with primary URL", e)
- errstr = e.message?.substringAfter("\":") ?: "empty message"
- }
- if (time == -1L) {
- try {
- time = coreController.measureDelay(SettingsManager.getDelayTestUrl(true))
- } catch (e: Exception) {
- Log.e(AppConfig.TAG, "Failed to measure delay with alternative URL", e)
- errstr = e.message?.substringAfter("\":") ?: "empty message"
- }
+ Log.e(AppConfig.TAG, "Failed to measure delay with alternative URL", e)
+ errorStr = e.message?.substringAfter("\":") ?: "empty message"
}
}
+
val result = if (time >= 0) {
service.getString(R.string.connection_test_available, time)
} else {
- service.getString(R.string.connection_test_error, errstr)
+ service.getString(R.string.connection_test_error, errorStr)
}
MessageUtil.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_SUCCESS, result)
@@ -296,16 +303,6 @@ object V2RayServiceManager {
}
}
- /**
- * Protects a socket from being routed through the VPN.
- * @param l The socket file descriptor.
- * @return True if protection was successful, false otherwise.
- */
- override fun protect(l: Long): Boolean {
- val serviceControl = serviceControl?.get() ?: return true
- return serviceControl.vpnProtect(l.toInt())
- }
-
/**
* Called when V2Ray core emits status information.
* @param l Status code.
diff --git a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt
index b7caa4e4fd..d9c3d86b8b 100644
--- a/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt
+++ b/v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/util/HttpUtil.kt
@@ -10,6 +10,7 @@ import java.io.IOException
import java.net.HttpURLConnection
import java.net.IDN
import java.net.InetAddress
+import java.net.Inet6Address
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.URL
@@ -40,37 +41,38 @@ object HttpUtil {
* @param ipv6Preferred Whether to prefer IPv6 addresses, defaults to false
* @return The resolved IP address or the original input (if it's already an IP or resolution fails)
*/
- fun resolveHostToIP(host: String, ipv6Preferred: Boolean = false): String {
+ fun resolveHostToIP(host: String, ipv6Preferred: Boolean = false): List {
try {
- // If it's already an IP address, return it directly
+ // If it's already an IP address, return it as a list
if (Utils.isPureIpAddress(host)) {
- return host
+ return listOf(host)
}
// Get all IP addresses
val addresses = InetAddress.getAllByName(host)
if (addresses.isEmpty()) {
- return host
+ return emptyList()
}
// Sort addresses based on preference
val sortedAddresses = if (ipv6Preferred) {
- // IPv6 preferred (size 16 first, then size 4)
- addresses.sortedByDescending { it.address.size }
+ addresses.sortedWith(compareByDescending { it is Inet6Address })
} else {
- // IPv4 preferred (size 4 first, then size 16)
- addresses.sortedBy { it.address.size }
+ addresses.sortedWith(compareBy { it is Inet6Address })
}
- Log.i(AppConfig.TAG, "Resolved IPs for $host: ${sortedAddresses.joinToString { it.hostAddress ?: "unknown" }}")
- // Return the first address after sorting
- return sortedAddresses.first().hostAddress ?: host
+ val ipList = sortedAddresses.mapNotNull { it.hostAddress }
+
+ Log.i(AppConfig.TAG, "Resolved IPs for $host: ${ipList.joinToString()}")
+
+ return ipList
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to resolve host to IP", e)
- return host
+ return emptyList()
}
}
+
/**
* Retrieves the content of a URL as a string.
*
diff --git a/xray-core/README.md b/xray-core/README.md
index 5090d6e82d..d0a2105986 100644
--- a/xray-core/README.md
+++ b/xray-core/README.md
@@ -102,6 +102,7 @@
- iOS & macOS arm64
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
+ - [Loon](https://apps.apple.com/us/app/loon/id1373567447)
- Xray Tools
- [xray-knife](https://github.com/lilendian0x00/xray-knife)
- [xray-checker](https://github.com/kutovoys/xray-checker)
@@ -114,10 +115,9 @@
- [XrayR](https://github.com/XrayR-project/XrayR)
- [XrayR-release](https://github.com/XrayR-project/XrayR-release)
- [XrayR-V2Board](https://github.com/missuo/XrayR-V2Board)
-- [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta)
- - [clashN](https://github.com/2dust/clashN)
- - [Clash Meta for Android](https://github.com/MetaCubeX/ClashMetaForAndroid)
-- [sing-box](https://github.com/SagerNet/sing-box)
+- Cores
+ - [mihomo](https://github.com/MetaCubeX/mihomo)
+ - [sing-box](https://github.com/SagerNet/sing-box)
## Contributing
diff --git a/xray-core/infra/conf/transport_internet.go b/xray-core/infra/conf/transport_internet.go
index e32be32669..b4ae080108 100644
--- a/xray-core/infra/conf/transport_internet.go
+++ b/xray-core/infra/conf/transport_internet.go
@@ -691,6 +691,7 @@ func (p TransportProtocol) Build() (string, error) {
}
type CustomSockoptConfig struct {
+ Syetem string `json:"system"`
Network string `json:"network"`
Level string `json:"level"`
Opt string `json:"opt"`
@@ -778,6 +779,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
for _, copt := range c.CustomSockopt {
customSockopt := &internet.CustomSockopt{
+ System: copt.Syetem,
Network: copt.Network,
Level: copt.Level,
Opt: copt.Opt,
diff --git a/xray-core/transport/internet/config.pb.go b/xray-core/transport/internet/config.pb.go
index 37a0814324..6aa11b3e91 100644
--- a/xray-core/transport/internet/config.pb.go
+++ b/xray-core/transport/internet/config.pb.go
@@ -417,11 +417,12 @@ type CustomSockopt struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"`
- Level string `protobuf:"bytes,2,opt,name=level,proto3" json:"level,omitempty"`
- Opt string `protobuf:"bytes,3,opt,name=opt,proto3" json:"opt,omitempty"`
- Value string `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"`
- Type string `protobuf:"bytes,5,opt,name=type,proto3" json:"type,omitempty"`
+ System string `protobuf:"bytes,1,opt,name=system,proto3" json:"system,omitempty"`
+ Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"`
+ Level string `protobuf:"bytes,3,opt,name=level,proto3" json:"level,omitempty"`
+ Opt string `protobuf:"bytes,4,opt,name=opt,proto3" json:"opt,omitempty"`
+ Value string `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"`
+ Type string `protobuf:"bytes,6,opt,name=type,proto3" json:"type,omitempty"`
}
func (x *CustomSockopt) Reset() {
@@ -454,6 +455,13 @@ func (*CustomSockopt) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
}
+func (x *CustomSockopt) GetSystem() string {
+ if x != nil {
+ return x.System
+ }
+ return ""
+}
+
func (x *CustomSockopt) GetNetwork() string {
if x != nil {
return x.Network
@@ -748,107 +756,108 @@ var file_transport_internet_config_proto_rawDesc = []byte{
0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x30, 0x0a, 0x13,
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72,
0x6f, 0x78, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73,
- 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x22, 0x7b,
- 0x0a, 0x0d, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74, 0x12,
- 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
- 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76,
- 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12,
- 0x10, 0x0a, 0x03, 0x6f, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6f, 0x70,
- 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
- 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
- 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xfd, 0x07, 0x0a, 0x0c,
- 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04,
- 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x6d, 0x61, 0x72, 0x6b,
- 0x12, 0x10, 0x0a, 0x03, 0x74, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x74,
- 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x06, 0x74, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x03, 0x20, 0x01,
- 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70,
- 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x6f, 0x63,
- 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79,
- 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x74, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x41, 0x0a, 0x1d,
- 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c,
- 0x5f, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20,
- 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x4f, 0x72, 0x69, 0x67,
- 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12,
- 0x21, 0x0a, 0x0c, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18,
- 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65,
- 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18,
- 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x62, 0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12,
- 0x32, 0x0a, 0x15, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f,
- 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13,
- 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f,
- 0x63, 0x6f, 0x6c, 0x12, 0x50, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74,
- 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x78,
- 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e,
- 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72,
- 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72,
- 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x61, 0x6c, 0x65, 0x72, 0x5f,
- 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x61,
- 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x35, 0x0a, 0x17, 0x74, 0x63, 0x70, 0x5f,
- 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72,
- 0x76, 0x61, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x74, 0x63, 0x70, 0x4b, 0x65,
- 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12,
- 0x2d, 0x0a, 0x13, 0x74, 0x63, 0x70, 0x5f, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76,
- 0x65, 0x5f, 0x69, 0x64, 0x6c, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x74, 0x63,
- 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x49, 0x64, 0x6c, 0x65, 0x12, 0x25,
- 0x0a, 0x0e, 0x74, 0x63, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e,
- 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x63, 0x70, 0x43, 0x6f, 0x6e, 0x67, 0x65,
- 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61,
- 0x63, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66,
- 0x61, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x36, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x0e, 0x20,
- 0x01, 0x28, 0x08, 0x52, 0x06, 0x76, 0x36, 0x6f, 0x6e, 0x6c, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x74,
- 0x63, 0x70, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x18,
- 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, 0x63, 0x70, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77,
- 0x43, 0x6c, 0x61, 0x6d, 0x70, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x63, 0x70, 0x5f, 0x75, 0x73, 0x65,
- 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52,
- 0x0e, 0x74, 0x63, 0x70, 0x55, 0x73, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12,
- 0x1e, 0x0a, 0x0b, 0x74, 0x63, 0x70, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x67, 0x18, 0x11,
- 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x63, 0x70, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x67, 0x12,
- 0x1c, 0x0a, 0x09, 0x70, 0x65, 0x6e, 0x65, 0x74, 0x72, 0x61, 0x74, 0x65, 0x18, 0x12, 0x20, 0x01,
- 0x28, 0x08, 0x52, 0x09, 0x70, 0x65, 0x6e, 0x65, 0x74, 0x72, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a,
- 0x09, 0x74, 0x63, 0x70, 0x5f, 0x6d, 0x70, 0x74, 0x63, 0x70, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08,
- 0x52, 0x08, 0x74, 0x63, 0x70, 0x4d, 0x70, 0x74, 0x63, 0x70, 0x12, 0x4c, 0x0a, 0x0d, 0x63, 0x75,
- 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74, 0x18, 0x14, 0x20, 0x03, 0x28,
- 0x0b, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f,
- 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74,
- 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f,
- 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74, 0x12, 0x60, 0x0a, 0x15, 0x61, 0x64, 0x64, 0x72,
- 0x65, 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
- 0x79, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74,
- 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65,
- 0x74, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x72,
- 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x13, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, 0x6f,
- 0x72, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0x2f, 0x0a, 0x0a, 0x54, 0x50,
- 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x66, 0x66, 0x10,
- 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x10, 0x01, 0x12, 0x0c, 0x0a,
- 0x08, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x10, 0x02, 0x2a, 0xa9, 0x01, 0x0a, 0x0e,
- 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x09,
- 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45,
- 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34,
- 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x03, 0x12,
- 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x36, 0x10, 0x04, 0x12, 0x0c, 0x0a,
- 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x34, 0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x46,
- 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52,
- 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x43,
- 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x08, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x4f, 0x52, 0x43, 0x45,
- 0x5f, 0x49, 0x50, 0x34, 0x36, 0x10, 0x09, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x4f, 0x52, 0x43, 0x45,
- 0x5f, 0x49, 0x50, 0x36, 0x34, 0x10, 0x0a, 0x2a, 0x97, 0x01, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x72,
- 0x65, 0x73, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12,
- 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x72, 0x76,
- 0x50, 0x6f, 0x72, 0x74, 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x72,
- 0x76, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x02, 0x12, 0x15,
- 0x0a, 0x11, 0x53, 0x72, 0x76, 0x50, 0x6f, 0x72, 0x74, 0x41, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72,
- 0x65, 0x73, 0x73, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x78, 0x74, 0x50, 0x6f, 0x72, 0x74,
- 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x78, 0x74, 0x41, 0x64, 0x64,
- 0x72, 0x65, 0x73, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x05, 0x12, 0x15, 0x0a, 0x11, 0x54, 0x78,
- 0x74, 0x50, 0x6f, 0x72, 0x74, 0x41, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10,
- 0x06, 0x42, 0x67, 0x0a, 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72,
- 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
- 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78,
- 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72,
- 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
- 0xaa, 0x02, 0x17, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
- 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
- 0x6f, 0x33,
+ 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x22, 0x93,
+ 0x01, 0x0a, 0x0d, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70, 0x74,
+ 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f,
+ 0x72, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x6f, 0x70, 0x74, 0x18,
+ 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6f, 0x70, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
+ 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
+ 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+ 0x74, 0x79, 0x70, 0x65, 0x22, 0xfd, 0x07, 0x0a, 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43,
+ 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x05, 0x52, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x66, 0x6f,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x06, 0x74,
+ 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x78, 0x72,
+ 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74,
+ 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
+ 0x69, 0x67, 0x2e, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x06, 0x74,
+ 0x70, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
+ 0x5f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x61,
+ 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65,
+ 0x63, 0x65, 0x69, 0x76, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73,
+ 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x69, 0x6e, 0x64,
+ 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b,
+ 0x62, 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62,
+ 0x69, 0x6e, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08,
+ 0x62, 0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x63, 0x63, 0x65,
+ 0x70, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
+ 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x50,
+ 0x72, 0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x50, 0x0a, 0x0f,
+ 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18,
+ 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61,
+ 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e,
+ 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e,
+ 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x21,
+ 0x0a, 0x0c, 0x64, 0x69, 0x61, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x09,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x61, 0x6c, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x78,
+ 0x79, 0x12, 0x35, 0x0a, 0x17, 0x74, 0x63, 0x70, 0x5f, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c,
+ 0x69, 0x76, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0a, 0x20, 0x01,
+ 0x28, 0x05, 0x52, 0x14, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65,
+ 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2d, 0x0a, 0x13, 0x74, 0x63, 0x70, 0x5f,
+ 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x64, 0x6c, 0x65, 0x18,
+ 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x74, 0x63, 0x70, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c,
+ 0x69, 0x76, 0x65, 0x49, 0x64, 0x6c, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x63, 0x70, 0x5f, 0x63,
+ 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x0d, 0x74, 0x63, 0x70, 0x43, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c,
+ 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06,
+ 0x76, 0x36, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x76, 0x36,
+ 0x6f, 0x6e, 0x6c, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x63, 0x70, 0x5f, 0x77, 0x69, 0x6e, 0x64,
+ 0x6f, 0x77, 0x5f, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e,
+ 0x74, 0x63, 0x70, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x43, 0x6c, 0x61, 0x6d, 0x70, 0x12, 0x28,
+ 0x0a, 0x10, 0x74, 0x63, 0x70, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f,
+ 0x75, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, 0x63, 0x70, 0x55, 0x73, 0x65,
+ 0x72, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x74, 0x63, 0x70, 0x5f,
+ 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x67, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74,
+ 0x63, 0x70, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x65, 0x6e, 0x65,
+ 0x74, 0x72, 0x61, 0x74, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x70, 0x65, 0x6e,
+ 0x65, 0x74, 0x72, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x63, 0x70, 0x5f, 0x6d, 0x70,
+ 0x74, 0x63, 0x70, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x63, 0x70, 0x4d, 0x70,
+ 0x74, 0x63, 0x70, 0x12, 0x4c, 0x0a, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63,
+ 0x6b, 0x6f, 0x70, 0x74, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61,
+ 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
+ 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f,
+ 0x70, 0x74, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x53, 0x6f, 0x63, 0x6b, 0x6f, 0x70,
+ 0x74, 0x12, 0x60, 0x0a, 0x15, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x72,
+ 0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0e,
+ 0x32, 0x2c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
+ 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,
+ 0x73, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x13,
+ 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74,
+ 0x65, 0x67, 0x79, 0x22, 0x2f, 0x0a, 0x0a, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x6f, 0x64,
+ 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x66, 0x66, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x50,
+ 0x72, 0x6f, 0x78, 0x79, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65,
+ 0x63, 0x74, 0x10, 0x02, 0x2a, 0xa9, 0x01, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53,
+ 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53,
+ 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b,
+ 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55,
+ 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f,
+ 0x49, 0x50, 0x34, 0x36, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50,
+ 0x36, 0x34, 0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50,
+ 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10,
+ 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x08,
+ 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x36, 0x10, 0x09,
+ 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x34, 0x10, 0x0a,
+ 0x2a, 0x97, 0x01, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, 0x6f, 0x72, 0x74,
+ 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65,
+ 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x72, 0x76, 0x50, 0x6f, 0x72, 0x74, 0x4f, 0x6e, 0x6c,
+ 0x79, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x72, 0x76, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
+ 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x72, 0x76, 0x50, 0x6f,
+ 0x72, 0x74, 0x41, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x03, 0x12, 0x0f,
+ 0x0a, 0x0b, 0x54, 0x78, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x4f, 0x6e, 0x6c, 0x79, 0x10, 0x04, 0x12,
+ 0x12, 0x0a, 0x0e, 0x54, 0x78, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4f, 0x6e, 0x6c,
+ 0x79, 0x10, 0x05, 0x12, 0x15, 0x0a, 0x11, 0x54, 0x78, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x41, 0x6e,
+ 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x06, 0x42, 0x67, 0x0a, 0x1b, 0x63, 0x6f,
+ 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
+ 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74,
+ 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61,
+ 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
+ 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x17, 0x58, 0x72, 0x61, 0x79,
+ 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72,
+ 0x6e, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
diff --git a/xray-core/transport/internet/config.proto b/xray-core/transport/internet/config.proto
index 54479e22b6..cb892c307f 100644
--- a/xray-core/transport/internet/config.proto
+++ b/xray-core/transport/internet/config.proto
@@ -65,11 +65,12 @@ message ProxyConfig {
}
message CustomSockopt {
- string network = 1;
- string level = 2;
- string opt = 3;
- string value = 4;
- string type = 5;
+ string system = 1;
+ string network = 2;
+ string level = 3;
+ string opt = 4;
+ string value = 5;
+ string type = 6;
}
// SocketConfig is options to be applied on network sockets.
diff --git a/xray-core/transport/internet/sockopt_darwin.go b/xray-core/transport/internet/sockopt_darwin.go
index f684de98ef..2c8272149a 100644
--- a/xray-core/transport/internet/sockopt_darwin.go
+++ b/xray-core/transport/internet/sockopt_darwin.go
@@ -1,8 +1,12 @@
package internet
import (
+ "context"
gonet "net"
"os"
+ "runtime"
+ "strconv"
+ "strings"
"syscall"
"unsafe"
@@ -147,6 +151,44 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
}
}
+ if len(config.CustomSockopt) > 0 {
+ for _, custom := range config.CustomSockopt {
+ if custom.System != "" && custom.System != runtime.GOOS {
+ errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS)
+ continue
+ }
+ // Skip unwanted network type
+ // network might be tcp4 or tcp6
+ // use HasPrefix so that "tcp" can match tcp4/6 with "tcp" if user want to control all tcp (udp is also the same)
+ // if it is empty, strings.HasPrefix will always return true to make it apply for all networks
+ if !strings.HasPrefix(network, custom.Network) {
+ continue
+ }
+ var level = 0x6 // default TCP
+ var opt int
+ if len(custom.Opt) == 0 {
+ return errors.New("No opt!")
+ } else {
+ opt, _ = strconv.Atoi(custom.Opt)
+ }
+ if custom.Level != "" {
+ level, _ = strconv.Atoi(custom.Level)
+ }
+ if custom.Type == "int" {
+ value, _ := strconv.Atoi(custom.Value)
+ if err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil {
+ return errors.New("failed to set CustomSockoptInt", opt, value, err)
+ }
+ } else if custom.Type == "str" {
+ if err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil {
+ return errors.New("failed to set CustomSockoptString", opt, custom.Value, err)
+ }
+ } else {
+ return errors.New("unknown CustomSockopt type:", custom.Type)
+ }
+ }
+ }
+
return nil
}
@@ -206,6 +248,44 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig)
}
}
+ if len(config.CustomSockopt) > 0 {
+ for _, custom := range config.CustomSockopt {
+ if custom.System != "" && custom.System != runtime.GOOS {
+ errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS)
+ continue
+ }
+ // Skip unwanted network type
+ // network might be tcp4 or tcp6
+ // use HasPrefix so that "tcp" can match tcp4/6 with "tcp" if user want to control all tcp (udp is also the same)
+ // if it is empty, strings.HasPrefix will always return true to make it apply for all networks
+ if !strings.HasPrefix(network, custom.Network) {
+ continue
+ }
+ var level = 0x6 // default TCP
+ var opt int
+ if len(custom.Opt) == 0 {
+ return errors.New("No opt!")
+ } else {
+ opt, _ = strconv.Atoi(custom.Opt)
+ }
+ if custom.Level != "" {
+ level, _ = strconv.Atoi(custom.Level)
+ }
+ if custom.Type == "int" {
+ value, _ := strconv.Atoi(custom.Value)
+ if err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil {
+ return errors.New("failed to set CustomSockoptInt", opt, value, err)
+ }
+ } else if custom.Type == "str" {
+ if err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil {
+ return errors.New("failed to set CustomSockoptString", opt, custom.Value, err)
+ }
+ } else {
+ return errors.New("unknown CustomSockopt type:", custom.Type)
+ }
+ }
+ }
+
return nil
}
diff --git a/xray-core/transport/internet/sockopt_linux.go b/xray-core/transport/internet/sockopt_linux.go
index 2d5877abcf..aa24cceb81 100644
--- a/xray-core/transport/internet/sockopt_linux.go
+++ b/xray-core/transport/internet/sockopt_linux.go
@@ -1,7 +1,9 @@
package internet
import (
+ "context"
"net"
+ "runtime"
"strconv"
"strings"
"syscall"
@@ -110,11 +112,15 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
if len(config.CustomSockopt) > 0 {
for _, custom := range config.CustomSockopt {
+ if custom.System != "" && custom.System != runtime.GOOS{
+ errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS)
+ continue
+ }
// Skip unwanted network type
// network might be tcp4 or tcp6
// use HasPrefix so that "tcp" can match tcp4/6 with "tcp" if user want to control all tcp (udp is also the same)
// if it is empty, strings.HasPrefix will always return true to make it apply for all networks
- if !strings.HasPrefix(network, custom.Network) {
+ if !strings.HasPrefix(network, custom.Network){
continue
}
var level = 0x6 // default TCP
@@ -212,6 +218,17 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig)
}
if len(config.CustomSockopt) > 0 {
for _, custom := range config.CustomSockopt {
+ if custom.System != "" && custom.System != runtime.GOOS{
+ errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS)
+ continue
+ }
+ // Skip unwanted network type
+ // network might be tcp4 or tcp6
+ // use HasPrefix so that "tcp" can match tcp4/6 with "tcp" if user want to control all tcp (udp is also the same)
+ // if it is empty, strings.HasPrefix will always return true to make it apply for all networks
+ if !strings.HasPrefix(network, custom.Network){
+ continue
+ }
var level = 0x6 // default TCP
var opt int
if len(custom.Opt) == 0 {
diff --git a/xray-core/transport/internet/sockopt_windows.go b/xray-core/transport/internet/sockopt_windows.go
index e333309f2e..fb8a970353 100644
--- a/xray-core/transport/internet/sockopt_windows.go
+++ b/xray-core/transport/internet/sockopt_windows.go
@@ -1,8 +1,12 @@
package internet
import (
+ "context"
"encoding/binary"
"net"
+ "runtime"
+ "strconv"
+ "strings"
"syscall"
"unsafe"
@@ -33,7 +37,10 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
if err != nil {
return errors.New("failed to find the interface").Base(err)
}
- isV4 := (network == "tcp4" || network == "udp4")
+ // easy way to check if the address is ipv4
+ isV4 := strings.Contains(address, ".")
+ // note: DO NOT trust the passed network variable, it can be udp6 even if the address is ipv4
+ // because operating system might(always) use ipv6 socket to process ipv4
if isV4 {
var bytes [4]byte
binary.BigEndian.PutUint32(bytes[:], uint32(inf.Index))
@@ -69,6 +76,42 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
}
}
+ if len(config.CustomSockopt) > 0 {
+ for _, custom := range config.CustomSockopt {
+ if custom.System != "" && custom.System != runtime.GOOS {
+ errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS)
+ continue
+ }
+ // Skip unwanted network type
+ // network might be tcp4 or tcp6
+ // use HasPrefix so that "tcp" can match tcp4/6 with "tcp" if user want to control all tcp (udp is also the same)
+ // if it is empty, strings.HasPrefix will always return true to make it apply for all networks
+ if !strings.HasPrefix(network, custom.Network) {
+ continue
+ }
+ var level = 0x6 // default TCP
+ var opt int
+ if len(custom.Opt) == 0 {
+ return errors.New("No opt!")
+ } else {
+ opt, _ = strconv.Atoi(custom.Opt)
+ }
+ if custom.Level != "" {
+ level, _ = strconv.Atoi(custom.Level)
+ }
+ if custom.Type == "int" {
+ value, _ := strconv.Atoi(custom.Value)
+ if err := syscall.SetsockoptInt(syscall.Handle(fd), level, opt, value); err != nil {
+ return errors.New("failed to set CustomSockoptInt", opt, value, err)
+ }
+ } else if custom.Type == "str" {
+ return errors.New("failed to set CustomSockoptString: Str type does not supported on windows")
+ } else {
+ return errors.New("unknown CustomSockopt type:", custom.Type)
+ }
+ }
+ }
+
return nil
}
@@ -94,6 +137,42 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig)
}
}
+ if len(config.CustomSockopt) > 0 {
+ for _, custom := range config.CustomSockopt {
+ if custom.System != "" && custom.System != runtime.GOOS {
+ errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS)
+ continue
+ }
+ // Skip unwanted network type
+ // network might be tcp4 or tcp6
+ // use HasPrefix so that "tcp" can match tcp4/6 with "tcp" if user want to control all tcp (udp is also the same)
+ // if it is empty, strings.HasPrefix will always return true to make it apply for all networks
+ if !strings.HasPrefix(network, custom.Network) {
+ continue
+ }
+ var level = 0x6 // default TCP
+ var opt int
+ if len(custom.Opt) == 0 {
+ return errors.New("No opt!")
+ } else {
+ opt, _ = strconv.Atoi(custom.Opt)
+ }
+ if custom.Level != "" {
+ level, _ = strconv.Atoi(custom.Level)
+ }
+ if custom.Type == "int" {
+ value, _ := strconv.Atoi(custom.Value)
+ if err := syscall.SetsockoptInt(syscall.Handle(fd), level, opt, value); err != nil {
+ return errors.New("failed to set CustomSockoptInt", opt, value, err)
+ }
+ } else if custom.Type == "str" {
+ return errors.New("failed to set CustomSockoptString: Str type does not supported on windows")
+ } else {
+ return errors.New("unknown CustomSockopt type:", custom.Type)
+ }
+ }
+ }
+
return nil
}
diff --git a/xray-core/transport/internet/splithttp/dialer.go b/xray-core/transport/internet/splithttp/dialer.go
index f996a42e5c..c5fba78bf4 100644
--- a/xray-core/transport/internet/splithttp/dialer.go
+++ b/xray-core/transport/internet/splithttp/dialer.go
@@ -281,11 +281,11 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
mode := transportConfiguration.Mode
if mode == "" || mode == "auto" {
mode = "packet-up"
- if httpVersion == "2" {
- mode = "stream-up"
- }
- if realityConfig != nil && transportConfiguration.DownloadSettings == nil {
+ if realityConfig != nil {
mode = "stream-one"
+ if transportConfiguration.DownloadSettings != nil {
+ mode = "stream-up"
+ }
}
}
diff --git a/xray-core/transport/internet/system_dialer.go b/xray-core/transport/internet/system_dialer.go
index cc8f3cc0a5..ba7db103a0 100644
--- a/xray-core/transport/internet/system_dialer.go
+++ b/xray-core/transport/internet/system_dialer.go
@@ -60,6 +60,10 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne
}
}
var lc net.ListenConfig
+ destAddr, err := net.ResolveUDPAddr("udp", dest.NetAddr())
+ if err != nil {
+ return nil, err
+ }
lc.Control = func(network, address string, c syscall.RawConn) error {
for _, ctl := range d.controllers {
if err := ctl(network, address, c); err != nil {
@@ -68,7 +72,7 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne
}
return c.Control(func(fd uintptr) {
if sockopt != nil {
- if err := applyOutboundSocketOptions(network, "", fd, sockopt); err != nil {
+ if err := applyOutboundSocketOptions(network, destAddr.String(), fd, sockopt); err != nil {
errors.LogInfo(ctx, err, "failed to apply socket options")
}
}
@@ -78,10 +82,6 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne
if err != nil {
return nil, err
}
- destAddr, err := net.ResolveUDPAddr("udp", dest.NetAddr())
- if err != nil {
- return nil, err
- }
return &PacketConnWrapper{
Conn: packetConn,
Dest: destAddr,
diff --git a/yt-dlp/README.md b/yt-dlp/README.md
index c0329f5394..0cc2cd7b2c 100644
--- a/yt-dlp/README.md
+++ b/yt-dlp/README.md
@@ -1770,7 +1770,7 @@ The following extractors use this feature:
* `lang`: Prefer translated metadata (`title`, `description` etc) of this language code (case-sensitive). By default, the video primary language metadata is preferred, with a fallback to `en` translated. See [youtube.py](https://github.com/yt-dlp/yt-dlp/blob/c26f9b991a0681fd3ea548d535919cec1fbbd430/yt_dlp/extractor/youtube.py#L381-L390) for list of supported content language codes
* `skip`: One or more of `hls`, `dash` or `translated_subs` to skip extraction of the m3u8 manifests, dash manifests and [auto-translated subtitles](https://github.com/yt-dlp/yt-dlp/issues/4090#issuecomment-1158102032) respectively
* `player_client`: Clients to extract video data from. The currently available clients are `web`, `web_safari`, `web_embedded`, `web_music`, `web_creator`, `mweb`, `ios`, `android`, `android_vr`, `tv` and `tv_embedded`. By default, `tv,ios,web` is used, or `tv,web` is used when authenticating with cookies. The `web_music` client is added for `music.youtube.com` URLs when logged-in cookies are used. The `tv_embedded` and `web_creator` clients are added for age-restricted videos if account age-verification is required. Some clients, such as `web` and `web_music`, require a `po_token` for their formats to be downloadable. Some clients, such as `web_creator`, will only work with authentication. Not all clients support authentication via cookies. You can use `default` for the default clients, or you can use `all` for all clients (not recommended). You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=default,-ios`
-* `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause some issues. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) for more details
+* `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player), `initial_data` (skip initial data/next ep request). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause issues such as missing formats or metadata. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) and [#12826](https://github.com/yt-dlp/yt-dlp/issues/12826) for more details
* `player_params`: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp.
* `comment_sort`: `top` or `new` (default) - choose comment sorting mode (on YouTube's side)
* `max_comments`: Limit the amount of comments to gather. Comma-separated list of integers representing `max-comments,max-parents,max-replies,max-replies-per-thread`. Default is `all,all,all,all`
diff --git a/yt-dlp/test/helper.py b/yt-dlp/test/helper.py
index 4169af799f..e4cb478e28 100644
--- a/yt-dlp/test/helper.py
+++ b/yt-dlp/test/helper.py
@@ -136,7 +136,7 @@ def _iter_differences(got, expected, field):
return
if op == 'startswith':
- if not val.startswith(got):
+ if not got.startswith(val):
yield field, f'should start with {val!r}, got {got!r}'
return
diff --git a/yt-dlp/test/test_networking.py b/yt-dlp/test/test_networking.py
index 3ab60fe836..2f441fced2 100644
--- a/yt-dlp/test/test_networking.py
+++ b/yt-dlp/test/test_networking.py
@@ -39,6 +39,7 @@ from yt_dlp.cookies import YoutubeDLCookieJar
from yt_dlp.dependencies import brotli, curl_cffi, requests, urllib3
from yt_dlp.networking import (
HEADRequest,
+ PATCHRequest,
PUTRequest,
Request,
RequestDirector,
@@ -1856,6 +1857,7 @@ class TestRequest:
def test_request_helpers(self):
assert HEADRequest('http://example.com').method == 'HEAD'
+ assert PATCHRequest('http://example.com').method == 'PATCH'
assert PUTRequest('http://example.com').method == 'PUT'
def test_headers(self):
diff --git a/yt-dlp/yt_dlp/extractor/cda.py b/yt-dlp/yt_dlp/extractor/cda.py
index 96f25c22a8..aa39bf3823 100644
--- a/yt-dlp/yt_dlp/extractor/cda.py
+++ b/yt-dlp/yt_dlp/extractor/cda.py
@@ -353,7 +353,7 @@ class CDAIE(InfoExtractor):
class CDAFolderIE(InfoExtractor):
_MAX_PAGE_SIZE = 36
- _VALID_URL = r'https?://(?:www\.)?cda\.pl/(?P\w+)/folder/(?P\d+)'
+ _VALID_URL = r'https?://(?:www\.)?cda\.pl/(?P[\w-]+)/folder/(?P\d+)'
_TESTS = [
{
'url': 'https://www.cda.pl/domino264/folder/31188385',
@@ -378,6 +378,9 @@ class CDAFolderIE(InfoExtractor):
'title': 'TESTY KOSMETYKÓW',
},
'playlist_mincount': 139,
+ }, {
+ 'url': 'https://www.cda.pl/FILMY-SERIALE-ANIME-KRESKOWKI-BAJKI/folder/18493422',
+ 'only_matching': True,
}]
def _real_extract(self, url):
diff --git a/yt-dlp/yt_dlp/extractor/youtube/_tab.py b/yt-dlp/yt_dlp/extractor/youtube/_tab.py
index 122300e600..c018ee8cfb 100644
--- a/yt-dlp/yt_dlp/extractor/youtube/_tab.py
+++ b/yt-dlp/yt_dlp/extractor/youtube/_tab.py
@@ -524,10 +524,16 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
response = self._extract_response(
item_id=f'{item_id} page {page_num}',
query=continuation, headers=headers, ytcfg=ytcfg,
- check_get_keys=('continuationContents', 'onResponseReceivedActions', 'onResponseReceivedEndpoints'))
+ check_get_keys=(
+ 'continuationContents', 'onResponseReceivedActions', 'onResponseReceivedEndpoints',
+ # Playlist recommendations may return with no data - ignore
+ ('responseContext', 'serviceTrackingParams', ..., 'params', ..., lambda k, v: k == 'key' and v == 'GetRecommendedMusicPlaylists_rid'),
+ ))
if not response:
break
+
+ continuation = None
# Extracting updated visitor data is required to prevent an infinite extraction loop in some cases
# See: https://github.com/ytdl-org/youtube-dl/issues/28702
visitor_data = self._extract_visitor_data(response) or visitor_data
@@ -564,7 +570,13 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
yield from func(video_items_renderer)
continuation = continuation_list[0] or self._extract_continuation(video_items_renderer)
- if not video_items_renderer:
+ # In the case only a continuation is returned, try to follow it.
+ # We extract this after trying to extract non-continuation items as otherwise this
+ # may be prioritized over other continuations.
+ # see: https://github.com/yt-dlp/yt-dlp/issues/12933
+ continuation = continuation or self._extract_continuation({'contents': [continuation_item]})
+
+ if not continuation and not video_items_renderer:
break
@staticmethod
@@ -999,14 +1011,14 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'playlist_mincount': 94,
'info_dict': {
'id': 'UCqj7Cz7revf5maW9g5pgNcg',
- 'title': 'Igor Kleiner Ph.D. - Playlists',
+ 'title': 'Igor Kleiner - Playlists',
'description': 'md5:15d7dd9e333cb987907fcb0d604b233a',
- 'uploader': 'Igor Kleiner Ph.D.',
+ 'uploader': 'Igor Kleiner ',
'uploader_id': '@IgorDataScience',
'uploader_url': 'https://www.youtube.com/@IgorDataScience',
- 'channel': 'Igor Kleiner Ph.D.',
+ 'channel': 'Igor Kleiner ',
'channel_id': 'UCqj7Cz7revf5maW9g5pgNcg',
- 'tags': ['критическое мышление', 'наука просто', 'математика', 'анализ данных'],
+ 'tags': 'count:23',
'channel_url': 'https://www.youtube.com/channel/UCqj7Cz7revf5maW9g5pgNcg',
'channel_follower_count': int,
},
@@ -1016,18 +1028,19 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'playlist_mincount': 94,
'info_dict': {
'id': 'UCqj7Cz7revf5maW9g5pgNcg',
- 'title': 'Igor Kleiner Ph.D. - Playlists',
+ 'title': 'Igor Kleiner - Playlists',
'description': 'md5:15d7dd9e333cb987907fcb0d604b233a',
- 'uploader': 'Igor Kleiner Ph.D.',
+ 'uploader': 'Igor Kleiner ',
'uploader_id': '@IgorDataScience',
'uploader_url': 'https://www.youtube.com/@IgorDataScience',
- 'tags': ['критическое мышление', 'наука просто', 'математика', 'анализ данных'],
+ 'tags': 'count:23',
'channel_id': 'UCqj7Cz7revf5maW9g5pgNcg',
- 'channel': 'Igor Kleiner Ph.D.',
+ 'channel': 'Igor Kleiner ',
'channel_url': 'https://www.youtube.com/channel/UCqj7Cz7revf5maW9g5pgNcg',
'channel_follower_count': int,
},
}, {
+ # TODO: fix channel_is_verified extraction
'note': 'playlists, series',
'url': 'https://www.youtube.com/c/3blue1brown/playlists?view=50&sort=dd&shelf_id=3',
'playlist_mincount': 5,
@@ -1066,22 +1079,23 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'url': 'https://www.youtube.com/c/ChristophLaimer/playlists',
'only_matching': True,
}, {
+ # TODO: fix availability extraction
'note': 'basic, single video playlist',
- 'url': 'https://www.youtube.com/playlist?list=PL4lCao7KL_QFVb7Iudeipvc2BCavECqzc',
+ 'url': 'https://www.youtube.com/playlist?list=PLt5yu3-wZAlSLRHmI1qNm0wjyVNWw1pCU',
'info_dict': {
- 'id': 'PL4lCao7KL_QFVb7Iudeipvc2BCavECqzc',
- 'title': 'youtube-dl public playlist',
+ 'id': 'PLt5yu3-wZAlSLRHmI1qNm0wjyVNWw1pCU',
+ 'title': 'single video playlist',
'description': '',
'tags': [],
'view_count': int,
- 'modified_date': '20201130',
- 'channel': 'Sergey M.',
- 'channel_id': 'UCmlqkdCBesrv2Lak1mF_MxA',
- 'channel_url': 'https://www.youtube.com/channel/UCmlqkdCBesrv2Lak1mF_MxA',
+ 'modified_date': '20250417',
+ 'channel': 'cole-dlp-test-acc',
+ 'channel_id': 'UCiu-3thuViMebBjw_5nWYrA',
+ 'channel_url': 'https://www.youtube.com/channel/UCiu-3thuViMebBjw_5nWYrA',
'availability': 'public',
- 'uploader': 'Sergey M.',
- 'uploader_url': 'https://www.youtube.com/@sergeym.6173',
- 'uploader_id': '@sergeym.6173',
+ 'uploader': 'cole-dlp-test-acc',
+ 'uploader_url': 'https://www.youtube.com/@coletdjnz',
+ 'uploader_id': '@coletdjnz',
},
'playlist_count': 1,
}, {
@@ -1171,11 +1185,11 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
},
'playlist_mincount': 17,
}, {
- 'note': 'Community tab',
+ 'note': 'Posts tab',
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/community',
'info_dict': {
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
- 'title': 'lex will - Community',
+ 'title': 'lex will - Posts',
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
'channel': 'lex will',
'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
@@ -1188,30 +1202,14 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
},
'playlist_mincount': 18,
}, {
- 'note': 'Channels tab',
- 'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/channels',
- 'info_dict': {
- 'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
- 'title': 'lex will - Channels',
- 'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
- 'channel': 'lex will',
- 'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
- 'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
- 'tags': ['bible', 'history', 'prophesy'],
- 'channel_follower_count': int,
- 'uploader_url': 'https://www.youtube.com/@lexwill718',
- 'uploader_id': '@lexwill718',
- 'uploader': 'lex will',
- },
- 'playlist_mincount': 12,
- }, {
+ # TODO: fix channel_is_verified extraction
'note': 'Search tab',
'url': 'https://www.youtube.com/c/3blue1brown/search?query=linear%20algebra',
'playlist_mincount': 40,
'info_dict': {
'id': 'UCYO_jab_esuFRV4b17AJtAw',
'title': '3Blue1Brown - Search - linear algebra',
- 'description': 'md5:4d1da95432004b7ba840ebc895b6b4c9',
+ 'description': 'md5:602e3789e6a0cb7d9d352186b720e395',
'channel_url': 'https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw',
'tags': ['Mathematics'],
'channel': '3Blue1Brown',
@@ -1232,6 +1230,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'url': 'https://music.youtube.com/channel/UCmlqkdCBesrv2Lak1mF_MxA',
'only_matching': True,
}, {
+ # TODO: fix availability extraction
'note': 'Playlist with deleted videos (#651). As a bonus, the video #51 is also twice in this list.',
'url': 'https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC',
'info_dict': {
@@ -1294,24 +1293,25 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
},
'playlist_mincount': 21,
}, {
+ # TODO: fix availability extraction
'note': 'Playlist with "show unavailable videos" button',
- 'url': 'https://www.youtube.com/playlist?list=UUTYLiWFZy8xtPwxFwX9rV7Q',
+ 'url': 'https://www.youtube.com/playlist?list=PLYwq8WOe86_xGmR7FrcJq8Sb7VW8K3Tt2',
'info_dict': {
- 'title': 'Uploads from Phim Siêu Nhân Nhật Bản',
- 'id': 'UUTYLiWFZy8xtPwxFwX9rV7Q',
+ 'title': 'The Memes Of 2010s.....',
+ 'id': 'PLYwq8WOe86_xGmR7FrcJq8Sb7VW8K3Tt2',
'view_count': int,
- 'channel': 'Phim Siêu Nhân Nhật Bản',
+ 'channel': "I'm Not JiNxEd",
'tags': [],
- 'description': '',
- 'channel_url': 'https://www.youtube.com/channel/UCTYLiWFZy8xtPwxFwX9rV7Q',
- 'channel_id': 'UCTYLiWFZy8xtPwxFwX9rV7Q',
+ 'description': 'md5:44dc3b315ba69394feaafa2f40e7b2a1',
+ 'channel_url': 'https://www.youtube.com/channel/UC5H5H85D1QE5-fuWWQ1hdNg',
+ 'channel_id': 'UC5H5H85D1QE5-fuWWQ1hdNg',
'modified_date': r're:\d{8}',
'availability': 'public',
- 'uploader_url': 'https://www.youtube.com/@phimsieunhannhatban',
- 'uploader_id': '@phimsieunhannhatban',
- 'uploader': 'Phim Siêu Nhân Nhật Bản',
+ 'uploader_url': 'https://www.youtube.com/@imnotjinxed1998',
+ 'uploader_id': '@imnotjinxed1998',
+ 'uploader': "I'm Not JiNxEd",
},
- 'playlist_mincount': 200,
+ 'playlist_mincount': 150,
'expected_warnings': [r'[Uu]navailable videos (are|will be) hidden'],
}, {
'note': 'Playlist with unavailable videos in page 7',
@@ -1334,6 +1334,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'playlist_mincount': 1000,
'expected_warnings': [r'[Uu]navailable videos (are|will be) hidden'],
}, {
+ # TODO: fix availability extraction
'note': 'https://github.com/ytdl-org/youtube-dl/issues/21844',
'url': 'https://www.youtube.com/playlist?list=PLzH6n4zXuckpfMu_4Ff8E7Z1behQks5ba',
'info_dict': {
@@ -1384,7 +1385,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
}, {
'url': 'https://www.youtube.com/channel/UCoMdktPbSTixAyNGwb-UYkQ/live',
'info_dict': {
- 'id': 'hGkQjiJLjWQ', # This will keep changing
+ 'id': 'YDvsBbKfLPA', # This will keep changing
'ext': 'mp4',
'title': str,
'upload_date': r're:\d{8}',
@@ -1409,6 +1410,8 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'uploader_id': '@SkyNews',
'uploader': 'Sky News',
'channel_is_verified': True,
+ 'media_type': 'livestream',
+ 'timestamp': int,
},
'params': {
'skip_download': True,
@@ -1496,6 +1499,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'url': 'https://music.youtube.com/browse/UC1a8OFewdjuLq6KlF8M_8Ng',
'only_matching': True,
}, {
+ # TODO: fix availability extraction
'note': 'VLPL, should redirect to playlist?list=PL...',
'url': 'https://music.youtube.com/browse/VLPLRBp0Fe2GpgmgoscNFLxNyBVSFVdYmFkq',
'info_dict': {
@@ -1537,6 +1541,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
}, {
# Destination channel with only a hidden self tab (tab id is UCtFRv9O2AHqOZjjynzrv-xg)
# Treat as a general feed
+ # TODO: fix extraction
'url': 'https://www.youtube.com/channel/UCtFRv9O2AHqOZjjynzrv-xg',
'info_dict': {
'id': 'UCtFRv9O2AHqOZjjynzrv-xg',
@@ -1560,21 +1565,21 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'expected_warnings': ['YouTube Music is not directly supported'],
}, {
'note': 'unlisted single video playlist',
- 'url': 'https://www.youtube.com/playlist?list=PLwL24UFy54GrB3s2KMMfjZscDi1x5Dajf',
+ 'url': 'https://www.youtube.com/playlist?list=PLt5yu3-wZAlQLfIN0MMgp0wVV6MP3bM4_',
'info_dict': {
- 'id': 'PLwL24UFy54GrB3s2KMMfjZscDi1x5Dajf',
- 'title': 'yt-dlp unlisted playlist test',
+ 'id': 'PLt5yu3-wZAlQLfIN0MMgp0wVV6MP3bM4_',
+ 'title': 'unlisted playlist',
'availability': 'unlisted',
'tags': [],
- 'modified_date': '20220418',
- 'channel': 'colethedj',
+ 'modified_date': '20250417',
+ 'channel': 'cole-dlp-test-acc',
'view_count': int,
'description': '',
- 'channel_id': 'UC9zHu_mHU96r19o-wV5Qs1Q',
- 'channel_url': 'https://www.youtube.com/channel/UC9zHu_mHU96r19o-wV5Qs1Q',
- 'uploader_url': 'https://www.youtube.com/@colethedj1894',
- 'uploader_id': '@colethedj1894',
- 'uploader': 'colethedj',
+ 'channel_id': 'UCiu-3thuViMebBjw_5nWYrA',
+ 'channel_url': 'https://www.youtube.com/channel/UCiu-3thuViMebBjw_5nWYrA',
+ 'uploader_url': 'https://www.youtube.com/@coletdjnz',
+ 'uploader_id': '@coletdjnz',
+ 'uploader': 'cole-dlp-test-acc',
},
'playlist': [{
'info_dict': {
@@ -1596,6 +1601,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'playlist_count': 1,
'params': {'extract_flat': True},
}, {
+ # By default, recommended is always empty.
'note': 'API Fallback: Recommended - redirects to home page. Requires visitorData',
'url': 'https://www.youtube.com/feed/recommended',
'info_dict': {
@@ -1603,7 +1609,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'title': 'recommended',
'tags': [],
},
- 'playlist_mincount': 50,
+ 'playlist_count': 0,
'params': {
'skip_download': True,
'extractor_args': {'youtubetab': {'skip': ['webpage']}},
@@ -1628,6 +1634,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
},
'skip': 'Query for sorting no longer works',
}, {
+ # TODO: fix 'unviewable' issue with this playlist when reloading with unavailable videos
'note': 'API Fallback: Topic, should redirect to playlist?list=UU...',
'url': 'https://music.youtube.com/browse/UC9ALqqC4aIeG5iDs7i90Bfw',
'info_dict': {
@@ -1654,11 +1661,12 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'url': 'https://www.youtube.com/channel/UCwVVpHQ2Cs9iGJfpdFngePQ',
'only_matching': True,
}, {
+ # TODO: fix metadata extraction
'note': 'collaborative playlist (uploader name in the form "by and x other(s)")',
'url': 'https://www.youtube.com/playlist?list=PLx-_-Kk4c89oOHEDQAojOXzEzemXxoqx6',
'info_dict': {
'id': 'PLx-_-Kk4c89oOHEDQAojOXzEzemXxoqx6',
- 'modified_date': '20220407',
+ 'modified_date': '20250115',
'channel_url': 'https://www.youtube.com/channel/UCKcqXmCcyqnhgpA5P0oHH_Q',
'tags': [],
'availability': 'unlisted',
@@ -1692,6 +1700,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'expected_warnings': ['Preferring "ja"'],
}, {
# XXX: this should really check flat playlist entries, but the test suite doesn't support that
+ # TODO: fix availability extraction
'note': 'preferred lang set with playlist with translated video titles',
'url': 'https://www.youtube.com/playlist?list=PLt5yu3-wZAlQAaPZ5Z-rJoTdbT-45Q7c0',
'info_dict': {
@@ -1714,6 +1723,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
}, {
# shorts audio pivot for 2GtVksBMYFM.
'url': 'https://www.youtube.com/feed/sfv_audio_pivot?bp=8gUrCikSJwoLMkd0VmtzQk1ZRk0SCzJHdFZrc0JNWUZNGgsyR3RWa3NCTVlGTQ==',
+ # TODO: fix extraction
'info_dict': {
'id': 'sfv_audio_pivot',
'title': 'sfv_audio_pivot',
@@ -1751,6 +1761,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'playlist_mincount': 8,
}, {
# Should get three playlists for videos, shorts and streams tabs
+ # TODO: fix channel_is_verified extraction
'url': 'https://www.youtube.com/channel/UCK9V2B22uJYu3N7eR_BT9QA',
'info_dict': {
'id': 'UCK9V2B22uJYu3N7eR_BT9QA',
@@ -1758,7 +1769,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'channel_follower_count': int,
'channel_id': 'UCK9V2B22uJYu3N7eR_BT9QA',
'channel_url': 'https://www.youtube.com/channel/UCK9V2B22uJYu3N7eR_BT9QA',
- 'description': 'md5:49809d8bf9da539bc48ed5d1f83c33f2',
+ 'description': 'md5:01e53f350ab8ad6fcf7c4fedb3c1b99f',
'channel': 'Polka Ch. 尾丸ポルカ',
'tags': 'count:35',
'uploader_url': 'https://www.youtube.com/@OmaruPolka',
@@ -1769,14 +1780,14 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'playlist_count': 3,
}, {
# Shorts tab with channel with handle
- # TODO: fix channel description
+ # TODO: fix channel_is_verified extraction
'url': 'https://www.youtube.com/@NotJustBikes/shorts',
'info_dict': {
'id': 'UC0intLFzLaudFG-xAvUEO-A',
'title': 'Not Just Bikes - Shorts',
'tags': 'count:10',
'channel_url': 'https://www.youtube.com/channel/UC0intLFzLaudFG-xAvUEO-A',
- 'description': 'md5:5e82545b3a041345927a92d0585df247',
+ 'description': 'md5:1d9fc1bad7f13a487299d1fe1712e031',
'channel_follower_count': int,
'channel_id': 'UC0intLFzLaudFG-xAvUEO-A',
'channel': 'Not Just Bikes',
@@ -1797,7 +1808,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'channel_url': 'https://www.youtube.com/channel/UC3eYAvjCVwNHgkaGbXX3sig',
'channel': '中村悠一',
'channel_follower_count': int,
- 'description': 'md5:e744f6c93dafa7a03c0c6deecb157300',
+ 'description': 'md5:e8fd705073a594f27d6d6d020da560dc',
'uploader_url': 'https://www.youtube.com/@Yuichi-Nakamura',
'uploader_id': '@Yuichi-Nakamura',
'uploader': '中村悠一',
@@ -1815,6 +1826,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'only_matching': True,
}, {
# No videos tab but has a shorts tab
+ # TODO: fix metadata extraction
'url': 'https://www.youtube.com/c/TKFShorts',
'info_dict': {
'id': 'UCgJ5_1F6yJhYLnyMszUdmUg',
@@ -1851,6 +1863,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
}, {
# Shorts url result in shorts tab
# TODO: Fix channel id extraction
+ # TODO: fix test suite, 208163447408c78673b08c172beafe5c310fb167 broke this test
'url': 'https://www.youtube.com/channel/UCiu-3thuViMebBjw_5nWYrA/shorts',
'info_dict': {
'id': 'UCiu-3thuViMebBjw_5nWYrA',
@@ -1879,6 +1892,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'params': {'extract_flat': True},
}, {
# Live video status should be extracted
+ # TODO: fix test suite, 208163447408c78673b08c172beafe5c310fb167 broke this test
'url': 'https://www.youtube.com/channel/UCQvWX73GQygcwXOTSf_VDVg/live',
'info_dict': {
'id': 'UCQvWX73GQygcwXOTSf_VDVg',
@@ -1907,6 +1921,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'playlist_mincount': 1,
}, {
# Channel renderer metadata. Contains number of videos on the channel
+ # TODO: channels tab removed, change this test to use another page with channel renderer
'url': 'https://www.youtube.com/channel/UCiu-3thuViMebBjw_5nWYrA/channels',
'info_dict': {
'id': 'UCiu-3thuViMebBjw_5nWYrA',
@@ -1940,7 +1955,9 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
},
}],
'params': {'extract_flat': True},
+ 'skip': 'channels tab removed',
}, {
+ # TODO: fix channel_is_verified extraction
'url': 'https://www.youtube.com/@3blue1brown/about',
'info_dict': {
'id': '@3blue1brown',
@@ -1950,7 +1967,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'channel_id': 'UCYO_jab_esuFRV4b17AJtAw',
'channel': '3Blue1Brown',
'channel_url': 'https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw',
- 'description': 'md5:4d1da95432004b7ba840ebc895b6b4c9',
+ 'description': 'md5:602e3789e6a0cb7d9d352186b720e395',
'uploader_url': 'https://www.youtube.com/@3blue1brown',
'uploader_id': '@3blue1brown',
'uploader': '3Blue1Brown',
@@ -1976,6 +1993,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'playlist_count': 5,
}, {
# Releases tab, with rich entry playlistRenderers (same as Podcasts tab)
+ # TODO: fix channel_is_verified extraction
'url': 'https://www.youtube.com/@AHimitsu/releases',
'info_dict': {
'id': 'UCgFwu-j5-xNJml2FtTrrB3A',
@@ -2015,6 +2033,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'playlist_mincount': 100,
'expected_warnings': [r'[Uu]navailable videos (are|will be) hidden'],
}, {
+ # TODO: fix channel_is_verified extraction
'note': 'Tags containing spaces',
'url': 'https://www.youtube.com/channel/UC7_YxT-KID8kRbqZo7MyscQ',
'playlist_count': 3,
@@ -2035,6 +2054,24 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'challenges', 'sketches', 'scary games', 'funny games', 'rage games',
'mark fischbach'],
},
+ }, {
+ # https://github.com/yt-dlp/yt-dlp/issues/12933
+ 'note': 'streams tab, some scheduled streams. Empty intermediate response with only continuation - must follow',
+ 'url': 'https://www.youtube.com/@sbcitygov/streams',
+ 'playlist_mincount': 150,
+ 'info_dict': {
+ 'id': 'UCH6-qfQwlUgz9SAf05jvc_w',
+ 'channel': 'sbcitygov',
+ 'channel_id': 'UCH6-qfQwlUgz9SAf05jvc_w',
+ 'title': 'sbcitygov - Live',
+ 'channel_follower_count': int,
+ 'description': 'md5:ca1a92059835c071e33b3db52f4a6d67',
+ 'uploader_id': '@sbcitygov',
+ 'uploader_url': 'https://www.youtube.com/@sbcitygov',
+ 'uploader': 'sbcitygov',
+ 'channel_url': 'https://www.youtube.com/channel/UCH6-qfQwlUgz9SAf05jvc_w',
+ 'tags': [],
+ },
}]
@classmethod
diff --git a/yt-dlp/yt_dlp/extractor/youtube/_video.py b/yt-dlp/yt_dlp/extractor/youtube/_video.py
index 074a2a0d8d..bcfe8b1520 100644
--- a/yt-dlp/yt_dlp/extractor/youtube/_video.py
+++ b/yt-dlp/yt_dlp/extractor/youtube/_video.py
@@ -3646,6 +3646,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
if 'sign in' in reason.lower():
reason = remove_end(reason, 'This helps protect our community. Learn more')
reason = f'{remove_end(reason.strip(), ".")}. {self._youtube_login_hint}'
+ elif get_first(playability_statuses, ('errorScreen', 'playerCaptchaViewModel', {dict})):
+ reason += '. YouTube is requiring a captcha challenge before playback'
self.raise_no_formats(reason, expected=True)
keywords = get_first(video_details, 'keywords', expected_type=list) or []
@@ -3874,7 +3876,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
if not traverse_obj(initial_data, 'contents'):
self.report_warning('Incomplete data received in embedded initial data; re-fetching using API.')
initial_data = None
- if not initial_data:
+ if not initial_data and 'initial_data' not in self._configuration_arg('player_skip'):
query = {'videoId': video_id}
query.update(self._get_checkok_params())
initial_data = self._extract_response(
diff --git a/yt-dlp/yt_dlp/networking/__init__.py b/yt-dlp/yt_dlp/networking/__init__.py
index 1eaa0ee5fd..39158a8cc1 100644
--- a/yt-dlp/yt_dlp/networking/__init__.py
+++ b/yt-dlp/yt_dlp/networking/__init__.py
@@ -3,6 +3,7 @@ import warnings
from .common import (
HEADRequest,
+ PATCHRequest,
PUTRequest,
Request,
RequestDirector,
diff --git a/yt-dlp/yt_dlp/networking/common.py b/yt-dlp/yt_dlp/networking/common.py
index ddceaa9a97..e33769422b 100644
--- a/yt-dlp/yt_dlp/networking/common.py
+++ b/yt-dlp/yt_dlp/networking/common.py
@@ -505,6 +505,7 @@ class Request:
HEADRequest = functools.partial(Request, method='HEAD')
+PATCHRequest = functools.partial(Request, method='PATCH')
PUTRequest = functools.partial(Request, method='PUT')