Update On Fri Apr 18 20:35:53 CEST 2025

This commit is contained in:
github-action[bot]
2025-04-18 20:35:54 +02:00
parent 7d10caf390
commit 3888f8359d
148 changed files with 6442 additions and 1253 deletions
+1
View File
@@ -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
+2
View File
@@ -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
-4
View File
@@ -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{
+77 -68
View File
@@ -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,
}
}
+3 -8
View File
@@ -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
}
+7 -12
View File
@@ -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)
}
+3 -5
View File
@@ -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
+36 -44
View File
@@ -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}
}
+7 -4
View File
@@ -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
}
+2 -2
View File
@@ -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,
}
+12 -4
View File
@@ -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)
+35 -23
View File
@@ -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
+12 -4
View File
@@ -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)
+3 -1
View File
@@ -32,7 +32,9 @@ func testSingMux(t *testing.T, tunnel *TestTunnel, out outbound.ProxyAdapter) {
Protocol: protocol,
}
out, err := outbound.NewSingMux(singMuxOption, &notCloseProxyAdapter{out})
assert.NoError(t, err)
if !assert.NoError(t, err) {
return
}
defer out.Close()
tunnel.DoTest(t, out)
@@ -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)
+12 -4
View File
@@ -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)
+12 -4
View File
@@ -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)
+12 -4
View File
@@ -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)
+12 -4
View File
@@ -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)
+1 -7
View File
@@ -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
+8 -4
View File
@@ -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
@@ -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)
}
}
+4 -125
View File
@@ -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 {
+12 -16
View File
@@ -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
}
+6 -2
View File
@@ -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)
@@ -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",
+3 -3
View File
@@ -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"
}
+1 -1
View File
@@ -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",
+69 -69
View File
@@ -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
@@ -182,6 +182,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
<Input
value={startup_script}
disabled
disableUnderline
sx={{ width: 230 }}
endAdornment={
<>
+1
View File
@@ -185,6 +185,7 @@
"Rule Provider": "مزود القواعد",
"Logs": "السجلات",
"Pause": "إيقاف مؤقت",
"Resume": "استأنف",
"Clear": "مسح",
"Test": "اختبار",
"Test All": "اختبار الكل",
+1
View File
@@ -189,6 +189,7 @@
"Rule Provider": "Rule Provider",
"Logs": "Logs",
"Pause": "Pause",
"Resume": "Resume",
"Clear": "Clear",
"Test": "Test",
"Test All": "Test All",
+1
View File
@@ -185,6 +185,7 @@
"Rule Provider": "تأمین‌کننده قانون",
"Logs": "لاگ‌ها",
"Pause": "توقف",
"Resume": "از سرگیری",
"Clear": "پاک کردن",
"Test": "آزمون",
"Test All": "آزمون همه",
+1
View File
@@ -217,6 +217,7 @@
"Rule Provider": "Penyedia Aturan",
"Logs": "Log",
"Pause": "Jeda",
"Resume": "Lanjut",
"Clear": "Bersihkan",
"Test": "Tes",
"Test All": "Tes Semua",
+1
View File
@@ -188,6 +188,7 @@
"Rule Provider": "Провайдеры правил",
"Logs": "Логи",
"Pause": "Пауза",
"Resume": "Возобновить",
"Clear": "Очистить",
"Test": "Тест",
"Test All": "Тестировать все",
+1
View File
@@ -185,6 +185,7 @@
"Rule Provider": "Кагыйдә провайдеры",
"Logs": "Логлар",
"Pause": "Туктау",
"Resume": "Дәвам",
"Clear": "Чистарту",
"Test": "Тест",
"Test All": "Барчасын тестлау",
+1
View File
@@ -189,6 +189,7 @@
"Rule Provider": "规则集合",
"Logs": "日志",
"Pause": "暂停",
"Resume": "继续",
"Clear": "清除",
"Test": "测试",
"Test All": "测试全部",
+1
View File
@@ -287,6 +287,7 @@ const Layout = () => {
<div className="layout__left">
<div className="the-logo" data-tauri-drag-region="true">
<div
data-tauri-drag-region="true"
style={{
height: "27px",
display: "flex",
+1 -1
View File
@@ -81,7 +81,7 @@ const LogPage = () => {
header={
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
<IconButton
title={t("Pause")}
title={t(enableLog ? "Pause" : "Resume")}
size="small"
color="inherit"
onClick={handleToggleLog}
+14
View File
@@ -42,6 +42,20 @@ jobs:
- name: Checkout OpenWrt
uses: actions/checkout@v4
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: true
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: true
swap-storage: true
- name: Update feeds
run: |
sed -i 's/#src-git helloworld/src-git helloworld/g' ./feeds.conf.default
+2 -2
View File
@@ -1,2 +1,2 @@
LINUX_VERSION-5.10 = .235
LINUX_KERNEL_HASH-5.10.235 = 953be3931101a94a93a644c1283ca41a7e567447ca87d3069ed4dd712dc1f1cc
LINUX_VERSION-5.10 = .236
LINUX_KERNEL_HASH-5.10.236 = 6da5cc8f7d39ed3acb4d59129a3f1570d981526ebbf58ea82595b7b6e000fb89
+2 -2
View File
@@ -1,2 +1,2 @@
LINUX_VERSION-5.15 = .179
LINUX_KERNEL_HASH-5.15.179 = 9319a47b1e9b5d344ff6015431856d0c9640e4faedc527c87f9129061a27136f
LINUX_VERSION-5.15 = .180
LINUX_KERNEL_HASH-5.15.180 = f51f68b8bbe60aca5e1ff3781f7e5d2ca6a31dd299c8446c39bf880bfff1cd39
+2 -2
View File
@@ -1,2 +1,2 @@
LINUX_VERSION-5.4 = .291
LINUX_KERNEL_HASH-5.4.291 = b3ad64a4476a7c5450b92eab9a888b84ecb64dc613fcb0128f653f58e958ef6e
LINUX_VERSION-5.4 = .292
LINUX_KERNEL_HASH-5.4.292 = 0bcbf580d1ea623ac5879d0f2d69796c82431b3f653c4749e63766dbf737be85
+2 -2
View File
@@ -23,13 +23,13 @@ PKG_CONFIG_DEPENDS += \
sanitize = $(call tolower,$(subst _,-,$(subst $(space),-,$(1))))
VERSION_NUMBER:=$(call qstrip,$(CONFIG_VERSION_NUMBER))
VERSION_NUMBER:=$(if $(VERSION_NUMBER),$(VERSION_NUMBER),24.10.0)
VERSION_NUMBER:=$(if $(VERSION_NUMBER),$(VERSION_NUMBER),24.10.1)
VERSION_CODE:=$(call qstrip,$(CONFIG_VERSION_CODE))
VERSION_CODE:=$(if $(VERSION_CODE),$(VERSION_CODE),$(REVISION))
VERSION_REPO:=$(call qstrip,$(CONFIG_VERSION_REPO))
VERSION_REPO:=$(if $(VERSION_REPO),$(VERSION_REPO),https://downloads.openwrt.org/releases/24.10.0)
VERSION_REPO:=$(if $(VERSION_REPO),$(VERSION_REPO),https://downloads.openwrt.org/releases/24.10.1)
VERSION_DIST:=$(call qstrip,$(CONFIG_VERSION_DIST))
VERSION_DIST:=$(if $(VERSION_DIST),$(VERSION_DIST),OpenWrt)
+1 -1
View File
@@ -190,7 +190,7 @@ if VERSIONOPT
config VERSION_REPO
string
prompt "Release repository"
default "https://downloads.openwrt.org/releases/24.10.0"
default "https://downloads.openwrt.org/releases/24.10.1"
help
This is the repository address embedded in the image, it defaults
to the trunk snapshot repo; the url may contain the following placeholders:
@@ -0,0 +1,20 @@
Suggested-by: Nicolas Frattaroli <frattaroli.nicolas@gmail.com>
Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
---
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",
};
---
@@ -0,0 +1,138 @@
Document Rockchip UFS host controller for RK3576 SoC.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
---
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 <shawn.lin@rock-chips.com>
+
+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 <dt-bindings/clock/rockchip,rk3576-cru.h>
+ #include <dt-bindings/reset/rockchip,rk3576-cru.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/power/rockchip,rk3576-power.h>
+ #include <dt-bindings/pinctrl/rockchip.h>
+ #include <dt-bindings/gpio/gpio.h>
+
+ 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 = <GIC_SPI 361 IRQ_TYPE_LEVEL_HIGH>;
+ 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
@@ -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 <shawn.lin@rock-chips.com>
---
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
@@ -0,0 +1,110 @@
From: Ulf Hansson <ulf.hansson@linaro.org>
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 <ulf.hansson@linaro.org>
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
---
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
@@ -0,0 +1,58 @@
Inform firmware to keep the power domain on or off.
Suggested-by: Ulf Hansson <ulf.hansson@linaro.org>
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
---
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 <linux/arm-smccc.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/err.h>
@@ -20,6 +21,7 @@
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
#include <soc/rockchip/pm_domains.h>
+#include <soc/rockchip/rockchip_sip.h>
#include <dt-bindings/power/px30-power.h>
#include <dt-bindings/power/rockchip,rv1126-power.h>
#include <dt-bindings/power/rk3036-power.h>
@@ -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
@@ -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 <shawn.lin@rock-chips.com>
---
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
@@ -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 <shawn.lin@rock-chips.com>
---
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 <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_wakeup.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include <ufs/ufshcd.h>
+#include <ufs/unipro.h>
+#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
@@ -0,0 +1,51 @@
Add ufshc node to rk3576.dtsi, so the board using UFS could
enable it.
Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com>
---
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 = <GIC_SPI 361 IRQ_TYPE_LEVEL_HIGH>;
+ 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
@@ -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 <heiko@sntech.de>
---
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
@@ -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 <heiko@sntech.de>
---
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
@@ -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 <heiko@sntech.de>
---
.../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
@@ -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 <heiko@sntech.de>
---
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
@@ -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 <heiko@sntech.de>
---
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
@@ -0,0 +1,237 @@
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
---
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
@@ -0,0 +1,412 @@
From 17e40c25158f2505cbcdeda96624afcbab4af368 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= <u.kleine-koenig@baylibre.com>
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 <u.kleine-koenig@baylibre.com>
Tested-by: Trevor Gamblin <tgamblin@baylibre.com>
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 <ukleinek@kernel.org>
---
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,
@@ -0,0 +1,327 @@
From 6c5126c6406d1c31e91f5b925c621c1c785366be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= <u.kleine-koenig@baylibre.com>
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 <u.kleine-koenig@baylibre.com>
Tested-by: Trevor Gamblin <tgamblin@baylibre.com>
Link: https://lore.kernel.org/r/6c97d27682853f603e18e9196043886dd671845d.1726819463.git.u.kleine-koenig@baylibre.com
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>
---
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);
@@ -0,0 +1,21 @@
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
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
@@ -0,0 +1,127 @@
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
.../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 <nicolas.frattaroli@collabora.com>
+
+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 <dt-bindings/clock/rockchip,rk3576-cru.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ 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 = <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>;
+ #pwm-cells = <3>;
+ };
+ };
+ - |
+ #include <dt-bindings/clock/rockchip,rk3576-cru.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ 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 = <GIC_SPI 111 IRQ_TYPE_LEVEL_HIGH>;
+ #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 <nicolas.frattaroli@collabora.com>
+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 <daniel@makrotopia.org>
M: Aurelien Jarno <aurelien@aurel32.net>
--
2.49.0
@@ -0,0 +1,90 @@
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
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 <nicolas.frattaroli@collabora.com>
+ */
+
+#ifndef __SOC_ROCKCHIP_UTILS_H__
+#define __SOC_ROCKCHIP_UTILS_H__
+
+#include <linux/bits.h>
+#include <linux/build_bug.h>
+#include <linux/limits.h>
+
+/*
+ * 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
@@ -0,0 +1,401 @@
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
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 <nicolas.frattaroli@collabora.com>
+ */
+
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <soc/rockchip/mfpwm.h>
+
+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 <nicolas.frattaroli@collabora.com>");
+MODULE_DESCRIPTION("Rockchip PWMv4 Driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("ROCKCHIP_MFPWM");
--
2.49.0
@@ -0,0 +1,403 @@
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
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 <nicolas.frattaroli@collabora.com>
+ */
+
+#include <linux/cleanup.h>
+#include <linux/counter.h>
+#include <linux/devm-helpers.h>
+#include <linux/interrupt.h>
+#include <linux/math.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <soc/rockchip/mfpwm.h>
+
+#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 <nicolas.frattaroli@collabora.com>");
+MODULE_DESCRIPTION("Rockchip PWM Counter Capture Driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("ROCKCHIP_MFPWM");
+MODULE_IMPORT_NS("COUNTER");
--
2.49.0
@@ -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;
@@ -0,0 +1,83 @@
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
---
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 <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/sizes.h>
@@ -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,
---
@@ -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 <supercatexpert@gmail.com>
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/rfkill.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/property.h>
+
+#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 <supercatexpert@gmail.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:rfkill-gpio-neo");
@@ -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);
}
@@ -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 = {
@@ -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 */
@@ -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 */
+2
View File
@@ -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
-4
View File
@@ -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{
+77 -68
View File
@@ -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,
}
}
+3 -8
View File
@@ -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
}
+7 -12
View File
@@ -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)
}
+3 -5
View File
@@ -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
+36 -44
View File
@@ -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}
}
+7 -4
View File
@@ -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
}
+2 -2
View File
@@ -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,
}
+12 -4
View File
@@ -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)
+35 -23
View File
@@ -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
+12 -4
View File
@@ -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)
+3 -1
View File
@@ -32,7 +32,9 @@ func testSingMux(t *testing.T, tunnel *TestTunnel, out outbound.ProxyAdapter) {
Protocol: protocol,
}
out, err := outbound.NewSingMux(singMuxOption, &notCloseProxyAdapter{out})
assert.NoError(t, err)
if !assert.NoError(t, err) {
return
}
defer out.Close()
tunnel.DoTest(t, out)
+12 -4
View File
@@ -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)
+12 -4
View File
@@ -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)
+12 -4
View File
@@ -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)
+12 -4
View File
@@ -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)
+12 -4
View File
@@ -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)
+1 -7
View File
@@ -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
+8 -4
View File
@@ -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
+13 -13
View File
@@ -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)
}
}
+4 -125
View File
@@ -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 {
+12 -16
View File
@@ -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
}
+6 -2
View File
@@ -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)
@@ -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
@@ -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('<span class="ping_value" cbiid="%s">---</span>', 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('<span class="tcping_value" cbiid="%s">---</span>', n)
end
if auto_detection_time ~= "tcping" then

Some files were not shown because too many files have changed in this diff Show More