Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PSC function support for DYTC 4.2+ #110

Open
Someone52 opened this issue Jan 18, 2021 · 9 comments
Open

PSC function support for DYTC 4.2+ #110

Someone52 opened this issue Jan 18, 2021 · 9 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@Someone52
Copy link

Someone52 commented Jan 18, 2021

Lenovo Thinkpad E590
When trying to set the modes, I get the following:

YSMC - Info: ThinkVPC::KHEY DYTCMode command 0x12b001 result: 0x0000000a (invalid argument) YSMC - Info: ThinkVPC::HKEY DYTCMode toggle failed

After a bit of investigation, i've decompiled the dytc method on my laptop, which is as follows:

            Local0 = Arg0
            Local1 = Zero
            ADBG (Concatenate ("DYTC STT=", ToHexString (Local0)))
            If ((WNTF && TATC))
            {
                Switch (ToInteger ((Local0 & 0x01FF)))
                {
                    Case (Zero)
                    {
                        Local1 = 0x0100
                        Local1 |= 0x50000000
                        Local1 |= Zero
                        Local1 |= One
                    }
                    Case (One)
                    {
                        Local2 = ((Local0 >> 0x0C) & 0x0F)
                        Local3 = ((Local0 >> 0x10) & 0x0F)
                        Local4 = ((Local0 >> 0x14) & One)
                        ADBG ("DYTC_CMD_SET")
                        ADBG (Concatenate ("ICFunc=", ToHexString (Local2)))
                        ADBG (Concatenate ("ICMode=", ToHexString (Local3)))
                        ADBG (Concatenate ("ValidF=", ToHexString (Local4)))
                        Local5 = (One << Local2)
                        If ((FCAP & Local5)){}
                        Else
                        {
                            Local1 = 0x06
                            ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                            Return (Local1)
                        }

                        Switch (Local2)
                        {
                            Case (One)
                            {
                                If ((Local3 != 0x0F))
                                {
                                    Local1 = 0x0A
                                    ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                    Return (Local1)
                                }

                                If ((Local4 == Zero))
                                {
                                    VCQL = Zero
                                }
                                Else
                                {
                                    VCQL = One
                                }
                            }
                            Case (0x04)
                            {
                                If ((Local3 != 0x0F))
                                {
                                    Local1 = 0x0A
                                    ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                    Return (Local1)
                                }

                                If ((Local4 == Zero))
                                {
                                    VSTP = Zero
                                }
                                Else
                                {
                                    VSTP = One
                                }
                            }
                            Case (0x0B)
                            {
                                Switch (Local3)
                                {
                                    Case (0x0F)
                                    {
                                        If ((Local4 != Zero))
                                        {
                                            Local1 = 0x0A
                                            ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                            Return (Local1)
                                        }
                                    }
                                    Default
                                    {
                                        Local1 = 0x0A
                                        ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                        Return (Local1)
                                    }

                                }

                                If ((Local4 == Zero))
                                {
                                    VMMC = Zero
                                    SMMC = Zero
                                }
                                Else
                                {
                                    VMMC = One
                                    SMMC = Local3
                                }
                            }
                            Case (0x0D)
                            {
                                If (((Local3 <= 0x08) && (Local3 >= One)))
                                {
                                    If ((Local4 != One))
                                    {
                                        Local1 = 0x0A
                                        ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                        Return (Local1)
                                    }
                                }
                                ElseIf ((Local3 == 0x0F))
                                {
                                    If ((Local4 != Zero))
                                    {
                                        Local1 = 0x0A
                                        ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                        Return (Local1)
                                    }
                                }
                                Else
                                {
                                    Local1 = 0x0A
                                    ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                    Return (Local1)
                                }

                                If ((Local4 == Zero))
                                {
                                    VPSC = Zero
                                    SPSC = Zero
                                }
                                Else
                                {
                                    VPSC = One
                                    SPSC = Local3
                                }
                            }
                            Case (Zero)
                            {
                                If ((Local3 != 0x0F))
                                {
                                    Local1 = 0x0A
                                    ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                    Return (Local1)
                                }
                            }
                            Default
                            {
                                Local1 = 0x02
                                ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                Return (Local1)
                            }

                        }

                        ADBG (" Set ODM Variable")
                        If (CondRefOf (\_SB.IETM.DPTE))
                        {
                            If ((^^^^^IETM.DPTE & One))
                            {
                                ODV0 = STDV /* \STDV */
                                ODV1 = VCQL /* \VCQL */
                                ODV2 = VTIO /* \VTIO */
                                If (((VMYH == One) && (SMYH == Zero)))
                                {
                                    ODV3 = One
                                }
                                Else
                                {
                                    ODV3 = Zero
                                }

                                If (((VMYH == One) && (SMYH == One)))
                                {
                                    ODV4 = One
                                }
                                Else
                                {
                                    ODV4 = Zero
                                }

                                If (((VMYH == One) && (SMYH == 0x02)))
                                {
                                    ODV5 = One
                                }
                                Else
                                {
                                    ODV5 = Zero
                                }

                                ODV6 = VSTP /* \VSTP */
                                ODV7 = VCQH /* \VCQH */
                                ODV8 = VDCC /* \VDCC */
                                ODV9 = VSFN /* \VSFN */
                                ODVA = VDMC /* \VDMC */
                                ODVB = VFHP /* \VFHP */
                                ODVC = VIFC /* \VIFC */
                                If (((VMMC == One) && (SMMC == Zero)))
                                {
                                    ODVD = One
                                }
                                Else
                                {
                                    ODVD = Zero
                                }

                                If (((VMMC == One) && (SMMC == One)))
                                {
                                    ODVE = One
                                }
                                Else
                                {
                                    ODVE = Zero
                                }

                                If (((VMMC == One) && (SMMC == 0x02)))
                                {
                                    ODVF = One
                                }
                                Else
                                {
                                    ODVF = Zero
                                }

                                If (((VMMC == One) && (SMMC == 0x03)))
                                {
                                    ODVH = One
                                }
                                Else
                                {
                                    ODVH = Zero
                                }

                                ODVG = VMSC /* \VMSC */
                                If ((VPSC == One))
                                {
                                    ODVI = SPSC /* \SPSC */
                                }
                                Else
                                {
                                    ODVI = Zero
                                }

                                ODVJ = VCSC /* \VCSC */
                                Notify (IETM, 0x88) // Device-Specific
                            }
                        }

                        If ((VSTP == One))
                        {
                            SCPF (0x04, DPST, One, Zero)
                        }
                        ElseIf ((VMMC == One))
                        {
                            If ((SMMC == Zero))
                            {
                                DMMC = MMOP /* \MMOP */
                                SCPF (0x0B, DMMC, Zero, Zero)
                            }
                            ElseIf ((SMMC == One))
                            {
                                DMMC = MMCO /* \MMCO */
                                SCPF (0x0B, DMMC, Zero, Zero)
                            }
                            ElseIf ((SMMC == 0x02))
                            {
                                DMMC = MMPE /* \MMPE */
                                SCPF (0x0B, DMMC, Zero, Zero)
                            }
                            ElseIf ((SMMC == 0x03))
                            {
                                DMMC = MMQU /* \MMQU */
                                SCPF (0x0B, DMMC, Zero, Zero)
                            }
                        }
                        ElseIf ((VCQL == One))
                        {
                            SCPF (One, DCQL, Zero, Zero)
                        }
                        ElseIf ((VPSC == One))
                        {
                            If ((SPSC == 0x08))
                            {
                                DPSC = PSM8 /* \PSM8 */
                            }
                            ElseIf ((SPSC == 0x07))
                            {
                                DPSC = PSM7 /* \PSM7 */
                            }
                            ElseIf ((SPSC == 0x06))
                            {
                                DPSC = PSM6 /* \PSM6 */
                            }
                            ElseIf ((SPSC == 0x05))
                            {
                                DPSC = PSM5 /* \PSM5 */
                            }
                            ElseIf ((SPSC == 0x04))
                            {
                                DPSC = PSM4 /* \PSM4 */
                            }
                            ElseIf ((SPSC == 0x03))
                            {
                                DPSC = PSM3 /* \PSM3 */
                            }
                            ElseIf ((SPSC == 0x02))
                            {
                                DPSC = PSM2 /* \PSM2 */
                            }
                            ElseIf ((SPSC == One))
                            {
                                DPSC = PSM1 /* \PSM1 */
                            }
                            Else
                            {
                                DPSC = PSM7 /* \PSM7 */
                            }

                            If ((SPSC <= 0x04))
                            {
                                SCPF (0x0D, DPSC, 0x05, Zero)
                            }
                            ElseIf (((SPSC <= 0x06) && (SPSC >= 0x05)))
                            {
                                SCPF (0x0D, DPSC, Zero, Zero)
                            }
                            ElseIf (((SPSC <= 0x08) && (SPSC >= 0x07)))
                            {
                                SCPF (0x0D, DPSC, 0x04, Zero)
                            }
                        }
                        Else
                        {
                            SCPF (Zero, DSTD, Zero, Zero)
                        }

                        Local5 = VSTD /* \VSTD */
                        Local5 |= (VCQL << One)
                        Local5 |= (VTIO << 0x02)
                        Local5 |= (VMYH << 0x03)
                        Local5 |= (VSTP << 0x04)
                        Local5 |= (VCQH << 0x05)
                        Local5 |= (VDCC << 0x06)
                        Local5 |= (VSFN << 0x07)
                        Local5 |= (VDMC << 0x08)
                        Local5 |= (VFHP << 0x09)
                        Local5 |= (VIFC << 0x0A)
                        Local5 |= (VMMC << 0x0B)
                        Local5 |= (VMSC << 0x0C)
                        Local5 |= (VPSC << 0x0D)
                        Local5 |= (VCSC << 0x0E)
                        Local1 = (CICF << 0x08)
                        If ((CICF == 0x03))
                        {
                            CICM = SMYH /* \SMYH */
                        }
                        ElseIf ((CICF == 0x0B))
                        {
                            CICM = SMMC /* \SMMC */
                        }
                        ElseIf ((CICF == 0x0D))
                        {
                            CICM = SPSC /* \SPSC */
                        }
                        Else
                        {
                            CICM = 0x0F
                        }

                        Local1 |= (CICM << 0x0C)
                        Local1 |= (Local5 << 0x10)
                        Local1 |= One
                        If (DHKC)
                        {
                            MHKQ (0x6032)
                        }
                    }
                    Case (0x02)
                    {
                        Local5 = VSTD /* \VSTD */
                        Local5 |= (VCQL << One)
                        Local5 |= (VTIO << 0x02)
                        Local5 |= (VMYH << 0x03)
                        Local5 |= (VSTP << 0x04)
                        Local5 |= (VCQH << 0x05)
                        Local5 |= (VDCC << 0x06)
                        Local5 |= (VSFN << 0x07)
                        Local5 |= (VDMC << 0x08)
                        Local5 |= (VFHP << 0x09)
                        Local5 |= (VIFC << 0x0A)
                        Local5 |= (VMMC << 0x0B)
                        Local5 |= (VMSC << 0x0C)
                        Local5 |= (VPSC << 0x0D)
                        Local5 |= (VCSC << 0x0E)
                        Local1 = (CICF << 0x08)
                        If ((CICF == 0x03))
                        {
                            CICM = SMYH /* \SMYH */
                        }
                        ElseIf ((CICF == 0x0B))
                        {
                            CICM = SMMC /* \SMMC */
                        }
                        ElseIf ((CICF == 0x0D))
                        {
                            CICM = SPSC /* \SPSC */
                        }
                        Else
                        {
                            CICM = 0x0F
                        }

                        Local1 |= (CICM << 0x0C)
                        Local1 |= (Local5 << 0x10)
                        Local1 |= One
                    }
                    Case (0x03)
                    {
                        Local1 = (FCAP << 0x10)
                        Local1 |= One
                    }
                    Case (0x04)
                    {
                        Local1 = (MYHC << 0x10)
                        Local1 |= One
                    }
                    Case (0x06)
                    {
                        Local2 = ((Local0 >> 0x09) & 0x0F)
                        If ((Local2 != One))
                        {
                            Local1 = (MMCC << 0x10)
                        }
                        Else
                        {
                            Local1 = 0x0200
                        }

                        Local1 |= One
                    }
                    Case (0x05)
                    {
                        If (Ones)
                        {
                            Local1 = 0x0500
                            Local1 |= 0x10E00000
                        }

                        Local1 |= One
                    }
                    Case (0x0100)
                    {
                        Local1 = 0x10010000
                        Local1 |= One
                    }
                    Case (0x01FF)
                    {
                        ADBG (" DYTC_CMD_RESET")
                        VCQL = Zero
                        VTIO = Zero
                        VMYH = Zero
                        VSTP = Zero
                        VCQH = Zero
                        VDCC = Zero
                        VSFN = Zero
                        VDMC = Zero
                        VFHP = Zero
                        VIFC = Zero
                        VMMC = Zero
                        VMSC = Zero
                        VPSC = Zero
                        VCSC = Zero
                        SMYH = Zero
                        SMMC = Zero
                        SPSC = Zero
                        SCPF (Zero, DSTD, Zero, Zero)
                        CICM = 0x0F
                        Local5 = VSTD /* \VSTD */
                        Local5 |= (VCQL << One)
                        Local5 |= (VTIO << 0x02)
                        Local5 |= (VMYH << 0x03)
                        Local5 |= (VSTP << 0x04)
                        Local5 |= (VCQH << 0x05)
                        Local5 |= (VDCC << 0x06)
                        Local5 |= (VSFN << 0x07)
                        Local5 |= (VDMC << 0x08)
                        Local5 |= (VFHP << 0x09)
                        Local5 |= (VIFC << 0x0A)
                        Local5 |= (VMMC << 0x0B)
                        Local5 |= (VMSC << 0x0C)
                        Local5 |= (VPSC << 0x0D)
                        Local5 |= (VCSC << 0x0E)
                        Local1 = (CICF << 0x08)
                        Local1 |= (CICM << 0x0C)
                        Local1 |= (Local5 << 0x10)
                        If (CondRefOf (\_SB.IETM.DPTE))
                        {
                            If ((^^^^^IETM.DPTE & One))
                            {
                                ODV0 = STDV /* \STDV */
                                ODV1 = Zero
                                ODV2 = Zero
                                ODV3 = Zero
                                ODV4 = Zero
                                ODV5 = Zero
                                ODV6 = Zero
                                ODV7 = Zero
                                ODV8 = Zero
                                ODV9 = Zero
                                ODVA = Zero
                                ODVB = Zero
                                ODVC = Zero
                                ODVD = Zero
                                ODVE = Zero
                                ODVF = Zero
                                ODVG = Zero
                                ODVH = Zero
                                ODVI = Zero
                                ODVJ = Zero
                                Notify (IETM, 0x88) // Device-Specific
                            }
                        }

                        Local1 |= One
                    }
                    Default
                    {
                        Local1 = 0x04
                    }

                }
            }
            Else
            {
                Local1 = 0x08
            }

            ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
            Return (Local1)
        }

As evident by the code here:

                            Case (0x0B)
                            {
                                Switch (Local3)
                                {
                                    Case (0x0F)
                                    {
                                        If ((Local4 != Zero))
                                        {
                                            Local1 = 0x0A
                                            ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                            Return (Local1)
                                        }
                                    }
                                    Default
                                    {
                                        Local1 = 0x0A
                                        ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                        Return (Local1)
                                    }

                                }

                                If ((Local4 == Zero))
                                {
                                    VMMC = Zero
                                    SMMC = Zero
                                }
                                Else
                                {
                                    VMMC = One
                                    SMMC = Local3
                                }
                            }

The method 0x0B ( which is #define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode, priority 3 */ ) does seem to be disabled and rejects all arguments apart from ICMode=0xF / ValidF = 0x0

Method method 0x0D ( which is not documented) however does seem to do change something (most likely the same performance modes)


                      Case (0x0D)
                            {
                                If (((Local3 <= 0x08) && (Local3 >= One)))
                                {
                                    If ((Local4 != One))
                                    {
                                        Local1 = 0x0A
                                        ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                        Return (Local1)
                                    }
                                }
                                ElseIf ((Local3 == 0x0F))
                                {
                                    If ((Local4 != Zero))
                                    {
                                        Local1 = 0x0A
                                        ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                        Return (Local1)
                                    }
                                }
                                Else
                                {
                                    Local1 = 0x0A
                                    ADBG (Concatenate ("DYTC END=", ToHexString (Local1)))
                                    Return (Local1)
                                }

                                If ((Local4 == Zero))
                                {
                                    VPSC = Zero
                                    SPSC = Zero
                                }
                                Else
                                {
                                    VPSC = One
                                    SPSC = Local3
                                }
                            }

@zhen-zen
Copy link
Owner

Available DYTC modes may vary in different models. However, only documented modes in https://patchwork.kernel.org/project/linux-acpi/patch/[email protected]/ is added. Other modes in DSDT are just made into unused macro.

If you are interested in further investigation, you can try following steps and maybe can find something useful.

  1. Reboot into windows / BIOS and change performance modes.
  2. Back into macOS and check raw DYTC status in the log (updated once during boot).
  3. Document the specific mode / function combinations. (Mine are just the same as the ones in the patch above)
  4. Try sending raw DYTC command, i.e. ioio -s ThinkVPC DYTCMode 0x12b001

@zhen-zen zhen-zen added enhancement New feature or request help wanted Extra attention is needed labels Jan 19, 2021
@Someone52
Copy link
Author

Someone52 commented Jan 19, 2021

After some more investigation, there seem to be 8 performance modes for the 0xD (13) command.

ioio -s ThinkVPC DYTCMode 0x11D001 ioio -s ThinkVPC DYTCMode 0x12D001 & etc

Modes 1-4 seem to be quiet, noticeably reducing the cooling fan speed (which is good because it seems like mode 4 is how it runs by default on windows)
1-3 seem to reduce cpu frequencies to 0,4ghz - 0,5ghz range which is definitely unusable and way too laggy.
4 reduces it to around 0,6ghz - 1,5 ghz, which seems to be optimal

5 and 6 seems to be how it runs by default, with fans blowing noticeably louder.
7 and 8 seem to let the cpu boost a little bit more and temperatures to climb higher, but need more testing just to be sure.

There is a catch though, it seems like the sequence of commands is very important.
Before sending the mode command, a disable has to be set:
ioio -s ThinkVPC DYTCMode 0x0FD001
And only then a performance mode can be set:
ioio -s ThinkVPC DYTCMode 0x14D001

Otherwise, if not doing this with proper sequence the laptop sometimes end up glitching and cpu seems to lock up at a clock of 0,4ghz and stay at that until proper sequence (or at least two 0x1FF resets) are sent.

Other functions (0x1 - lapmode, 0x4 - ??) seem to have no noticeable effect.

Edit: after futher testing on my i7-8565u and monitoring speeds/temps/etc

Modes 1-4:

  • Reduced fan speed,
  • Thermal throttling starts at 55-60C
  • CPU boost seems to be disabled
  • Mode 1 seems to be unusable due to cpu running most of the time at 0,4ghz

Modes 5-6:

  • Normal fan speed
  • Thermal throttling starts at 75-80C
  • Less CPU boost on average under heavy loud (tested with prime95)

Modes 7-8

  • Normal fan speed
  • Thermal throttling starts at 78-80C
  • More CPU boost on average under heavy loud (tested with prime95)

@Someone52
Copy link
Author

I have made a pull request containing the implementation of my findings.
#111

@zhen-zen zhen-zen changed the title Lenovo Thinkpad E590 DYTC Issues DYTC PSC mode support Jan 20, 2021
@zhen-zen zhen-zen changed the title DYTC PSC mode support PSC function support for DYTC 4.2+ Jan 20, 2021
@zhen-zen
Copy link
Owner

zhen-zen commented Jan 20, 2021

Thank you for implementing support for this function. The PR looks good and maybe we can carry out preciser control based on DYTC version.

PSC function is also spotted on X1 Extreme and X1C6. Both machine has DYTC version 4.2, but the latter one has the following restriction:

If (((Local3 != 0x02) && ((Local3 != 0x07) && (Local3 != 0x08))))
{
    ADBG ("PSC InValid Mode, Clear the PSC State.")
    Local4 = 0x00
}

So I'm not sure if this function is ready on 4.2 version.

@1Revenger1 @tylernguyen @benbender Does current mode control work on your laptop? And how about the linked PR by @Someone52 ?

And prior to version 4.2, it seems that PSC mode is not supported. The 4.1 one from E580 even has few modes than mine (4.0). Maybe we can judge the availability of PSC mode by DYTC version?

The most complete DYTC method I have seen is obtained from Yoga C940 (15)

STD (0x0): 0xf, - // standard
CQL (0x1): 0xf, 0/1 // lap (check specific mode under desk/MMC)
TIO (0x2): 0xf, 0/1
MYH (0x3):
	TBL: 0x0, 1
	TNT: 0x1, 1
	LFT: 0x2, 1
	MASK: 0xf, 0
STP (0x4): 0xf, 0/1
CQH (0x5): 0xf, 0/1
DCC (0x6): 0xf, 0/1
SFN (0x7): 0xf, 0/1
DMC (0x8): 0xf, 0/1
FHP (0x9): 0xf, 0/1
IFC (0xa): 0xf, 0/1
MMC (0xb): // desk
	PFM: 0x2, 1 // performance
	QUT: 0x3, 1 // quiet
	MASK: 0xf, 1 // balance
MSC (0xc): 0xf, 0/1
PSC (0xd): 
	0x1-0x8, 1
	0xf, 0
CSC (0xe): 0xf, 0/1

Currently I don't have many samples on DYTC 5.0 and newer. But at least yours is covered by the list above.

@1Revenger1
Copy link
Contributor

1Revenger1 commented Jan 20, 2021

Just tested on my device and it appears to function correctly. The changes are reflected in IORegistryExplorer and no errors are created when looking through log show --last boot | grep -i ysmc. There doesn't seem to really be any change on how my device uses the fans, though that was the case with the old implementation of DYTC as well. Additionally, I am able to select Quiet values (before this PR, if I set quiet, it would revert back to Normal). Here is a picture from IOReg:
image

Edit: I just now realized that this should've gone under the PR, oops

@zhen-zen
Copy link
Owner

zhen-zen commented Jan 20, 2021

The normal mode is called MMC AUTO mode in Yoga C940 (15) example above. It seems that on X1E and X1C6, supported mode is 1/2/f instead of 3/2/f for quiet/performance/balance (aka AUTO). I don't know how to distinguish them, maybe some different command below may help that.

For version 5.0, only normal mode is supported for E590 and X1C7 don't have MMC function at all.

And it's quite confusing that they still stick to the MMC function in https://patchwork.kernel.org/project/platform-driver-x86/patch/[email protected]/ where requires DYTC version 5 and newer.

@tylernguyen
Copy link
Contributor

tylernguyen commented Jan 24, 2021

@zhen-zen

The current mode control doesn't work on my x1c6. It's limited to only Balance or Performance. The same thing goes for the new PR.

@zhen-zen
Copy link
Owner

@zhen-zen

The current mode control doesn't work on my x1c6. It's limited to only Balance or Performance. The same thing goes for the new PR.

I suppose at least 2/7/8 should work for your model. So it's a bit strange here. When you are available, can you check the initial DYTC result in debug log after switching to different thermal mode in Vantage / BIOS?

@zhen-zen
Copy link
Owner

zhen-zen commented Feb 7, 2021

Finally got some time for #116 . Please try that and I will merge both PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

4 participants