mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-22 16:07:49 +08:00
Update On Fri Apr 18 20:35:53 CEST 2025
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -32,7 +32,9 @@ func testSingMux(t *testing.T, tunnel *TestTunnel, out outbound.ProxyAdapter) {
|
||||
Protocol: protocol,
|
||||
}
|
||||
out, err := outbound.NewSingMux(singMuxOption, ¬CloseProxyAdapter{out})
|
||||
assert.NoError(t, err)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
tunnel.DoTest(t, out)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Generated
+69
-69
@@ -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={
|
||||
<>
|
||||
|
||||
@@ -185,6 +185,7 @@
|
||||
"Rule Provider": "مزود القواعد",
|
||||
"Logs": "السجلات",
|
||||
"Pause": "إيقاف مؤقت",
|
||||
"Resume": "استأنف",
|
||||
"Clear": "مسح",
|
||||
"Test": "اختبار",
|
||||
"Test All": "اختبار الكل",
|
||||
|
||||
@@ -189,6 +189,7 @@
|
||||
"Rule Provider": "Rule Provider",
|
||||
"Logs": "Logs",
|
||||
"Pause": "Pause",
|
||||
"Resume": "Resume",
|
||||
"Clear": "Clear",
|
||||
"Test": "Test",
|
||||
"Test All": "Test All",
|
||||
|
||||
@@ -185,6 +185,7 @@
|
||||
"Rule Provider": "تأمینکننده قانون",
|
||||
"Logs": "لاگها",
|
||||
"Pause": "توقف",
|
||||
"Resume": "از سرگیری",
|
||||
"Clear": "پاک کردن",
|
||||
"Test": "آزمون",
|
||||
"Test All": "آزمون همه",
|
||||
|
||||
@@ -217,6 +217,7 @@
|
||||
"Rule Provider": "Penyedia Aturan",
|
||||
"Logs": "Log",
|
||||
"Pause": "Jeda",
|
||||
"Resume": "Lanjut",
|
||||
"Clear": "Bersihkan",
|
||||
"Test": "Tes",
|
||||
"Test All": "Tes Semua",
|
||||
|
||||
@@ -188,6 +188,7 @@
|
||||
"Rule Provider": "Провайдеры правил",
|
||||
"Logs": "Логи",
|
||||
"Pause": "Пауза",
|
||||
"Resume": "Возобновить",
|
||||
"Clear": "Очистить",
|
||||
"Test": "Тест",
|
||||
"Test All": "Тестировать все",
|
||||
|
||||
@@ -185,6 +185,7 @@
|
||||
"Rule Provider": "Кагыйдә провайдеры",
|
||||
"Logs": "Логлар",
|
||||
"Pause": "Туктау",
|
||||
"Resume": "Дәвам",
|
||||
"Clear": "Чистарту",
|
||||
"Test": "Тест",
|
||||
"Test All": "Барчасын тестлау",
|
||||
|
||||
@@ -189,6 +189,7 @@
|
||||
"Rule Provider": "规则集合",
|
||||
"Logs": "日志",
|
||||
"Pause": "暂停",
|
||||
"Resume": "继续",
|
||||
"Clear": "清除",
|
||||
"Test": "测试",
|
||||
"Test All": "测试全部",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
+20
@@ -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",
|
||||
};
|
||||
|
||||
|
||||
---
|
||||
+138
@@ -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
|
||||
+31
@@ -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
|
||||
+110
@@ -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
|
||||
+58
@@ -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
|
||||
+68
@@ -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
|
||||
+515
@@ -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
|
||||
+51
@@ -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
|
||||
+412
@@ -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,
|
||||
+327
@@ -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);
|
||||
+21
@@ -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
|
||||
+127
@@ -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
|
||||
+90
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
+403
@@ -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
|
||||
+13
@@ -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;
|
||||
|
||||
+83
@@ -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);
|
||||
}
|
||||
+38
@@ -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 */
|
||||
Vendored
+2
@@ -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
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -32,7 +32,9 @@ func testSingMux(t *testing.T, tunnel *TestTunnel, out outbound.ProxyAdapter) {
|
||||
Protocol: protocol,
|
||||
}
|
||||
out, err := outbound.NewSingMux(singMuxOption, ¬CloseProxyAdapter{out})
|
||||
assert.NoError(t, err)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
tunnel.DoTest(t, out)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user