From 3888f8359d9c65b2efa78088ea7973bd2d8f00ac Mon Sep 17 00:00:00 2001 From: "github-action[bot]" Date: Fri, 18 Apr 2025 20:35:54 +0200 Subject: [PATCH] Update On Fri Apr 18 20:35:53 CEST 2025 --- .github/update.log | 1 + clash-meta/.github/workflows/test.yml | 2 + clash-meta/adapter/outbound/anytls.go | 4 - clash-meta/adapter/outbound/trojan.go | 145 +- clash-meta/adapter/outbound/vless.go | 11 +- clash-meta/adapter/outbound/vmess.go | 19 +- clash-meta/adapter/parser.go | 8 +- clash-meta/component/dialer/dialer.go | 80 +- clash-meta/component/dialer/options.go | 11 +- clash-meta/component/tls/reality.go | 4 +- clash-meta/listener/inbound/anytls_test.go | 16 +- clash-meta/listener/inbound/common_test.go | 58 +- clash-meta/listener/inbound/hysteria2_test.go | 16 +- clash-meta/listener/inbound/mux_test.go | 4 +- .../listener/inbound/shadowsocks_test.go | 16 +- clash-meta/listener/inbound/trojan_test.go | 16 +- clash-meta/listener/inbound/tuic_test.go | 16 +- clash-meta/listener/inbound/vless_test.go | 16 +- clash-meta/listener/inbound/vmess_test.go | 16 +- clash-meta/transport/anytls/client.go | 8 +- clash-meta/transport/gun/gun.go | 12 +- .../transport/sing-shadowtls/shadowtls.go | 26 +- clash-meta/transport/trojan/trojan.go | 129 +- clash-meta/transport/vmess/tls.go | 28 +- clash-meta/transport/vmess/websocket.go | 8 +- clash-nyanpasu/frontend/nyanpasu/package.json | 8 +- clash-nyanpasu/manifest/version.json | 6 +- clash-nyanpasu/package.json | 2 +- clash-nyanpasu/pnpm-lock.yaml | 138 +- .../setting/setting-verge-basic.tsx | 1 + clash-verge-rev/src/locales/ar.json | 1 + clash-verge-rev/src/locales/en.json | 1 + clash-verge-rev/src/locales/fa.json | 1 + clash-verge-rev/src/locales/id.json | 1 + clash-verge-rev/src/locales/ru.json | 1 + clash-verge-rev/src/locales/tt.json | 1 + clash-verge-rev/src/locales/zh.json | 1 + clash-verge-rev/src/pages/_layout.tsx | 1 + clash-verge-rev/src/pages/logs.tsx | 2 +- lede/.github/workflows/openwrt-ci.yml | 14 + lede/include/kernel-5.10 | 4 +- lede/include/kernel-5.15 | 4 +- lede/include/kernel-5.4 | 4 +- lede/include/version.mk | 4 +- lede/package/base-files/image-config.in | 2 +- ...p-mark-hclk-vi-as-critical-on-rk3568.patch | 20 + ...al-support-for-rk3576-ufs-controller.patch | 138 ++ ...al-support-for-rk3576-ufs-controller.patch | 31 + ...al-support-for-rk3576-ufs-controller.patch | 110 ++ ...al-support-for-rk3576-ufs-controller.patch | 58 + ...al-support-for-rk3576-ufs-controller.patch | 68 + ...al-support-for-rk3576-ufs-controller.patch | 515 +++++++ ...al-support-for-rk3576-ufs-controller.patch | 51 + .../312-01-v6.13-rk3576-otp-support.patch | 23 + .../312-02-v6.13-rk3576-otp-support.patch | 52 + .../312-03-v6.13-rk3576-otp-support.patch | 49 + .../312-04-v6.13-rk3576-otp-support.patch | 40 + .../312-05-v6.13-rk3576-otp-support.patch | 60 + .../340-01-pwm-add-more-locking.patch | 237 ++++ ...wm-new-abstraction-for-pwm-waveforms.patch | 412 ++++++ ...consumer-API-functions-for-waveforms.patch | 327 +++++ ...rease-mas-amount-of-device-functions.patch | 21 + ...-new-binding-for-rockchip-rk3576-pwm.patch | 127 ++ ...der-for-things-shared-across-drivers.patch | 90 ++ ...341-04-soc-rockchip-add-mfpwm-driver.patch | 1181 +++++++++++++++++ ...341-05-pwm-add-rockchip-pwmv4-driver.patch | 401 ++++++ ...nter-add-rockchip-pwm-capture-driver.patch | 403 ++++++ ...4-add-device-set-of-node-from-parent.patch | 13 + ...-dwcmshc-add-pd-workaround-on-rk3576.patch | 83 ++ .../993-add-rfkill-gpio-neo-driver.patch | 294 ++++ .../996-set-led-mode-for-yt8521.patch | 46 + ...p-naneng-combo-phy-add-sgmii-mac-sel.patch | 38 + ...d-CPU-model-number-for-Bartlett-Lake.patch | 11 + ...d-CPU-model-number-for-Bartlett-Lake.patch | 11 + mihomo/.github/workflows/test.yml | 2 + mihomo/adapter/outbound/anytls.go | 4 - mihomo/adapter/outbound/trojan.go | 145 +- mihomo/adapter/outbound/vless.go | 11 +- mihomo/adapter/outbound/vmess.go | 19 +- mihomo/adapter/parser.go | 8 +- mihomo/component/dialer/dialer.go | 80 +- mihomo/component/dialer/options.go | 11 +- mihomo/component/tls/reality.go | 4 +- mihomo/listener/inbound/anytls_test.go | 16 +- mihomo/listener/inbound/common_test.go | 58 +- mihomo/listener/inbound/hysteria2_test.go | 16 +- mihomo/listener/inbound/mux_test.go | 4 +- mihomo/listener/inbound/shadowsocks_test.go | 16 +- mihomo/listener/inbound/trojan_test.go | 16 +- mihomo/listener/inbound/tuic_test.go | 16 +- mihomo/listener/inbound/vless_test.go | 16 +- mihomo/listener/inbound/vmess_test.go | 16 +- mihomo/transport/anytls/client.go | 8 +- mihomo/transport/gun/gun.go | 12 +- mihomo/transport/sing-shadowtls/shadowtls.go | 26 +- mihomo/transport/trojan/trojan.go | 129 +- mihomo/transport/vmess/tls.go | 28 +- mihomo/transport/vmess/websocket.go | 8 +- .../model/cbi/passwall/client/global.lua | 45 +- .../model/cbi/passwall/client/node_list.lua | 14 +- .../luci-app-passwall/po/zh-cn/passwall.po | 6 + .../root/usr/share/passwall/app.sh | 14 +- .../share/passwall/helper_chinadns_add.lua | 5 + shadowsocks-rust/Cargo.lock | 21 +- .../luci-static/resources/tools/nikki.js | 9 +- .../luci-static/resources/view/nikki/proxy.js | 6 + .../root/usr/share/rpcd/ucode/luci.nikki | 15 +- .../model/cbi/passwall/client/global.lua | 45 +- .../model/cbi/passwall/client/node_list.lua | 14 +- small/luci-app-passwall/po/zh-cn/passwall.po | 6 + .../root/usr/share/passwall/app.sh | 14 +- .../share/passwall/helper_chinadns_add.lua | 5 + small/nikki/Makefile | 8 +- small/nikki/files/scripts/debug.sh | 169 ++- .../ViewModels/MainWindowViewModel.cs | 7 +- .../ViewModels/StatusBarViewModel.cs | 5 +- v2rayng/AndroidLibXrayLite/.gitignore | 1 + v2rayng/AndroidLibXrayLite/libv2ray_main.go | 17 +- .../java/com/v2ray/ang/dto/ProfileLiteItem.kt | 9 - .../java/com/v2ray/ang/dto/V2rayConfig.kt | 33 +- .../main/java/com/v2ray/ang/fmt/FmtBase.kt | 4 - .../main/java/com/v2ray/ang/fmt/HttpFmt.kt | 2 +- .../java/com/v2ray/ang/fmt/ShadowsocksFmt.kt | 2 +- .../main/java/com/v2ray/ang/fmt/SocksFmt.kt | 2 +- .../main/java/com/v2ray/ang/fmt/TrojanFmt.kt | 2 +- .../main/java/com/v2ray/ang/fmt/VlessFmt.kt | 2 +- .../main/java/com/v2ray/ang/fmt/VmessFmt.kt | 2 +- .../java/com/v2ray/ang/fmt/WireguardFmt.kt | 2 +- .../v2ray/ang/handler/V2rayConfigManager.kt | 52 +- .../v2ray/ang/service/V2RayServiceManager.kt | 53 +- .../main/java/com/v2ray/ang/util/HttpUtil.kt | 26 +- xray-core/README.md | 8 +- xray-core/infra/conf/transport_internet.go | 2 + xray-core/transport/internet/config.pb.go | 221 +-- xray-core/transport/internet/config.proto | 11 +- .../transport/internet/sockopt_darwin.go | 80 ++ xray-core/transport/internet/sockopt_linux.go | 19 +- .../transport/internet/sockopt_windows.go | 81 +- .../transport/internet/splithttp/dialer.go | 8 +- xray-core/transport/internet/system_dialer.go | 10 +- yt-dlp/README.md | 2 +- yt-dlp/test/helper.py | 2 +- yt-dlp/test/test_networking.py | 2 + yt-dlp/yt_dlp/extractor/cda.py | 5 +- yt-dlp/yt_dlp/extractor/youtube/_tab.py | 175 ++- yt-dlp/yt_dlp/extractor/youtube/_video.py | 4 +- yt-dlp/yt_dlp/networking/__init__.py | 1 + yt-dlp/yt_dlp/networking/common.py | 1 + 148 files changed, 6442 insertions(+), 1253 deletions(-) create mode 100644 lede/target/linux/rockchip/patches-6.12/002-clk-rockchip-mark-hclk-vi-as-critical-on-rk3568.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/311-01-v6.13-initial-support-for-rk3576-ufs-controller.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/311-02-v6.13-initial-support-for-rk3576-ufs-controller.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/311-03-v6.13-initial-support-for-rk3576-ufs-controller.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/311-04-v6.13-initial-support-for-rk3576-ufs-controller.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/311-05-v6.13-initial-support-for-rk3576-ufs-controller.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/311-06-v6.13-initial-support-for-rk3576-ufs-controller.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/311-07-v6.13-initial-support-for-rk3576-ufs-controller.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/312-01-v6.13-rk3576-otp-support.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/312-02-v6.13-rk3576-otp-support.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/312-03-v6.13-rk3576-otp-support.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/312-04-v6.13-rk3576-otp-support.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/312-05-v6.13-rk3576-otp-support.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/340-01-pwm-add-more-locking.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/340-02-pwm-new-abstraction-for-pwm-waveforms.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/340-03-pwm-provide-new-consumer-API-functions-for-waveforms.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/341-01-dt-bindings-pinctrl-rockchip-increase-mas-amount-of-device-functions.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/341-02-dt-bindings-pwm-add-a-new-binding-for-rockchip-rk3576-pwm.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/341-03-soc-rockchip-add-utils-header-for-things-shared-across-drivers.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/341-04-soc-rockchip-add-mfpwm-driver.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/341-05-pwm-add-rockchip-pwmv4-driver.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/341-06-counter-add-rockchip-pwm-capture-driver.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/341-07-pwm-pwmv4-add-device-set-of-node-from-parent.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/342-mmc-sdhci-of-dwcmshc-add-pd-workaround-on-rk3576.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/993-add-rfkill-gpio-neo-driver.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/996-set-led-mode-for-yt8521.patch create mode 100644 lede/target/linux/rockchip/patches-6.12/997-rockchip-naneng-combo-phy-add-sgmii-mac-sel.patch create mode 100644 lede/target/linux/x86/patches-6.12/900-add-CPU-model-number-for-Bartlett-Lake.patch create mode 100644 lede/target/linux/x86/patches-6.6/900-add-CPU-model-number-for-Bartlett-Lake.patch delete mode 100644 v2rayng/V2rayNG/app/src/main/java/com/v2ray/ang/dto/ProfileLiteItem.kt 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')