[net,3/3] net: lan743x: Address problems with wake option flags configuration sequences

Message ID 20240226080934.46003-4-Raju.Lakkaraju@microchip.com
State New
Headers
Series net: lan743x: Fixes for multiple WOL related issues |

Commit Message

Raju Lakkaraju Feb. 26, 2024, 8:09 a.m. UTC
  Wake options handling has been reworked as follows:
a. We only enable secure on magic packet when both secure and magic wol
   options are requested together.
b. If secure-on magic packet had been previously enabled, and a subsequent
   command does not include it, we add it. This was done to workaround a
   problem with the 'pm-suspend' application which is unaware of secure-on
   magic packet being enabled and can unintentionally disable it prior to
   putting the system into suspend.

Fixes: 6b3768ac8e2b3 ("net: lan743x: Add support to Secure-ON WOL")
Signed-off-by: Raju Lakkaraju <Raju.Lakkaraju@microchip.com>
---
 drivers/net/ethernet/microchip/lan743x_ethtool.c | 10 ++++++++++
 1 file changed, 10 insertions(+)
  

Comments

Andrew Lunn Feb. 27, 2024, 1:58 a.m. UTC | #1
On Mon, Feb 26, 2024 at 01:39:34PM +0530, Raju Lakkaraju wrote:
> Wake options handling has been reworked as follows:
> a. We only enable secure on magic packet when both secure and magic wol
>    options are requested together.
> b. If secure-on magic packet had been previously enabled, and a subsequent
>    command does not include it, we add it. This was done to workaround a
>    problem with the 'pm-suspend' application which is unaware of secure-on
>    magic packet being enabled and can unintentionally disable it prior to
>    putting the system into suspend.

This seems to be in a bit of a grey area. But ethtool says:

           wol p|u|m|b|a|g|s|f|d...
                  Sets  Wake-on-LAN  options.   Not  all devices support this.
                  The argument to this option is a string of characters speci‐
                  fying which options to enable.
                  p   Wake on PHY activity
                  u   Wake on unicast messages
                  m   Wake on multicast messages
                  b   Wake on broadcast messages
                  a   Wake on ARP
                  g   Wake on MagicPacket™
                  s   Enable SecureOn™ password for MagicPacket™
                  f   Wake on filter(s)
                  d   Disable (wake on  nothing).   This  option
                      clears all previous options.

d clears everything. All other things enable one option. There does
not appear to be a way to disable a single option, and i would say,
adding options is incremental.

So:

> a. We only enable secure on magic packet when both secure and magic wol
>    options are requested together.

I don't think they need to be requested together. I think you can
first enable Wake on MagicPacket and then in a second call to ethtool
Enable SecureOn password for MagicPacket. I also don't think it would
unreasonable to accept Enable SecureOn password for MagicPacket and
have that imply Wake on MagicPacket.

And:

> b. If secure-on magic packet had been previously enabled, and a subsequent
>    command does not include it, we add it.

If there has not been a d, secure-on magic packet should remain
enabled until there is a d.

	Andrew
  
Raju Lakkaraju Feb. 29, 2024, 8:59 a.m. UTC | #2
Hi Andrew,

Thank you for review comments.

> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Tuesday, February 27, 2024 7:28 AM
> To: Raju Lakkaraju - I30499 <Raju.Lakkaraju@microchip.com>
> Cc: netdev@vger.kernel.org; davem@davemloft.net; kuba@kernel.org; linux-
> kernel@vger.kernel.org; Bryan Whitehead - C21958
> <Bryan.Whitehead@microchip.com>; richardcochran@gmail.com;
> UNGLinuxDriver <UNGLinuxDriver@microchip.com>
> Subject: Re: [PATCH net 3/3] net: lan743x: Address problems with wake option
> flags configuration sequences
> 
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the
> content is safe
> 
> On Mon, Feb 26, 2024 at 01:39:34PM +0530, Raju Lakkaraju wrote:
> > Wake options handling has been reworked as follows:
> > a. We only enable secure on magic packet when both secure and magic wol
> >    options are requested together.
> > b. If secure-on magic packet had been previously enabled, and a
> subsequent
> >    command does not include it, we add it. This was done to workaround a
> >    problem with the 'pm-suspend' application which is unaware of secure-on
> >    magic packet being enabled and can unintentionally disable it prior to
> >    putting the system into suspend.
> 
> This seems to be in a bit of a grey area. But ethtool says:
> 
>            wol p|u|m|b|a|g|s|f|d...
>                   Sets  Wake-on-LAN  options.   Not  all devices support this.
>                   The argument to this option is a string of characters speci‐
>                   fying which options to enable.
>                   p   Wake on PHY activity
>                   u   Wake on unicast messages
>                   m   Wake on multicast messages
>                   b   Wake on broadcast messages
>                   a   Wake on ARP
>                   g   Wake on MagicPacket™
>                   s   Enable SecureOn™ password for MagicPacket™
>                   f   Wake on filter(s)
>                   d   Disable (wake on  nothing).   This  option
>                       clears all previous options.
> 
> d clears everything. All other things enable one option. There does not
> appear to be a way to disable a single option, and i would say, adding options
> is incremental.
> 

Yes. "d" clears everything.
But, if we enable "g" then enable "a", "g" option overwritten by "a"
Please find the attached log information 
> So:
> 
> > a. We only enable secure on magic packet when both secure and magic wol
> >    options are requested together.
> 
> I don't think they need to be requested together. I think you can first enable
> Wake on MagicPacket and then in a second call to ethtool Enable SecureOn
> password for MagicPacket. I also don't think it would unreasonable to accept
> Enable SecureOn password for MagicPacket and have that imply Wake on
> MagicPacket.
> 

If we need to enable any 2 options, we have to provide both options together.
i.e.
sudo ethtool -s enp9s0 wol ga

> And:
> 
> > b. If secure-on magic packet had been previously enabled, and a
> subsequent
> >    command does not include it, we add it.
> 
> If there has not been a d, secure-on magic packet should remain enabled until
> there is a d.
> 

This is not happened with existing "Ethtool".
Please find the log information in an attachment file.

>         Andrew

Thanks,
Raju
$ sudo ethtool -s enp9s0
Settings for enp9s0:
	Supported ports: [  ]
	Supported link modes:   10baseT/Half 10baseT/Full
	                        100baseT/Half 100baseT/Full
	                        1000baseT/Full
	                        2500baseT/Full
	Supported pause frame use: Symmetric Receive-only
	Supports auto-negotiation: Yes
	Supported FEC modes: Not reported
	Advertised link modes:  10baseT/Half 10baseT/Full
	                        100baseT/Half 100baseT/Full
	                        1000baseT/Full
	                        2500baseT/Full
	Advertised pause frame use: Symmetric Receive-only
	Advertised auto-negotiation: Yes
	Advertised FEC modes: Not reported
	Link partner advertised link modes:  10baseT/Half 10baseT/Full
	                                     100baseT/Half 100baseT/Full
	                                     1000baseT/Full
	                                     2500baseT/Full
	Link partner advertised pause frame use: Symmetric Receive-only
	Link partner advertised auto-negotiation: No
	Link partner advertised FEC modes: Not reported
	Speed: 2500Mb/s
	Duplex: Full
	Auto-negotiation: on
	master-slave cfg: preferred slave
	master-slave status: master
	Port: Twisted Pair
	PHYAD: 1
	Transceiver: external
	MDI-X: on (auto)
	Supports Wake-on: pumbags
	Wake-on: d
        SecureOn password: 00:00:00:00:00:00
        Current message level: 0x00000137 (311)
                               drv probe link ifdown ifup tx_queued
	Link detected: yes

$ sudo ethtool -s enp9s0 wol g
$ sudo ethtool enp9s0
Settings for enp9s0:
	Supported ports: [  ]
	Supported link modes:   10baseT/Half 10baseT/Full
	                        100baseT/Half 100baseT/Full
	                        1000baseT/Full
	                        2500baseT/Full
	Supported pause frame use: Symmetric Receive-only
	Supports auto-negotiation: Yes
	Supported FEC modes: Not reported
	Advertised link modes:  10baseT/Half 10baseT/Full
	                        100baseT/Half 100baseT/Full
	                        1000baseT/Full
	                        2500baseT/Full
	Advertised pause frame use: Symmetric Receive-only
	Advertised auto-negotiation: Yes
	Advertised FEC modes: Not reported
	Link partner advertised link modes:  10baseT/Half 10baseT/Full
	                                     100baseT/Half 100baseT/Full
	                                     1000baseT/Full
	                                     2500baseT/Full
	Link partner advertised pause frame use: Symmetric Receive-only
	Link partner advertised auto-negotiation: No
	Link partner advertised FEC modes: Not reported
	Speed: 2500Mb/s
	Duplex: Full
	Auto-negotiation: on
	master-slave cfg: preferred slave
	master-slave status: master
	Port: Twisted Pair
	PHYAD: 1
	Transceiver: external
	MDI-X: on (auto)
	Supports Wake-on: pumbags
	Wake-on: g
        SecureOn password: 00:00:00:00:00:00
        Current message level: 0x00000137 (311)
                               drv probe link ifdown ifup tx_queued
	Link detected: yes

$ sudo ethtool -s enp9s0 wol a
$ sudo ethtool enp9s0
Settings for enp9s0:
	Supported ports: [  ]
	Supported link modes:   10baseT/Half 10baseT/Full
	                        100baseT/Half 100baseT/Full
	                        1000baseT/Full
	                        2500baseT/Full
	Supported pause frame use: Symmetric Receive-only
	Supports auto-negotiation: Yes
	Supported FEC modes: Not reported
	Advertised link modes:  10baseT/Half 10baseT/Full
	                        100baseT/Half 100baseT/Full
	                        1000baseT/Full
	                        2500baseT/Full
	Advertised pause frame use: Symmetric Receive-only
	Advertised auto-negotiation: Yes
	Advertised FEC modes: Not reported
	Link partner advertised link modes:  10baseT/Half 10baseT/Full
	                                     100baseT/Half 100baseT/Full
	                                     1000baseT/Full
	                                     2500baseT/Full
	Link partner advertised pause frame use: Symmetric Receive-only
	Link partner advertised auto-negotiation: No
	Link partner advertised FEC modes: Not reported
	Speed: 2500Mb/s
	Duplex: Full
	Auto-negotiation: on
	master-slave cfg: preferred slave
	master-slave status: master
	Port: Twisted Pair
	PHYAD: 1
	Transceiver: external
	MDI-X: on (auto)
	Supports Wake-on: pumbags
	Wake-on: a
        SecureOn password: 00:00:00:00:00:00
        Current message level: 0x00000137 (311)
                               drv probe link ifdown ifup tx_queued
	Link detected: yes

$ sudo ethtool -s enp9s0 wol ga
$ sudo ethtool enp9s0
Settings for enp9s0:
	Supported ports: [  ]
	Supported link modes:   10baseT/Half 10baseT/Full
	                        100baseT/Half 100baseT/Full
	                        1000baseT/Full
	                        2500baseT/Full
	Supported pause frame use: Symmetric Receive-only
	Supports auto-negotiation: Yes
	Supported FEC modes: Not reported
	Advertised link modes:  10baseT/Half 10baseT/Full
	                        100baseT/Half 100baseT/Full
	                        1000baseT/Full
	                        2500baseT/Full
	Advertised pause frame use: Symmetric Receive-only
	Advertised auto-negotiation: Yes
	Advertised FEC modes: Not reported
	Link partner advertised link modes:  10baseT/Half 10baseT/Full
	                                     100baseT/Half 100baseT/Full
	                                     1000baseT/Full
	                                     2500baseT/Full
	Link partner advertised pause frame use: Symmetric Receive-only
	Link partner advertised auto-negotiation: No
	Link partner advertised FEC modes: Not reported
	Speed: 2500Mb/s
	Duplex: Full
	Auto-negotiation: on
	master-slave cfg: preferred slave
	master-slave status: master
	Port: Twisted Pair
	PHYAD: 1
	Transceiver: external
	MDI-X: on (auto)
	Supports Wake-on: pumbags
	Wake-on: ag
        SecureOn password: 00:00:00:00:00:00
        Current message level: 0x00000137 (311)
                               drv probe link ifdown ifup tx_queued
	Link detected: yes

raju-project-pc@ ~$ sudo ethtool -s enp9s0 wol b
raju-project-pc@ ~$ sudo ethtool enp9s0
Settings for enp9s0:
	Supported ports: [  ]
	Supported link modes:   10baseT/Half 10baseT/Full
	                        100baseT/Half 100baseT/Full
	                        1000baseT/Full
	                        2500baseT/Full
	Supported pause frame use: Symmetric Receive-only
	Supports auto-negotiation: Yes
	Supported FEC modes: Not reported
	Advertised link modes:  10baseT/Half 10baseT/Full
	                        100baseT/Half 100baseT/Full
	                        1000baseT/Full
	                        2500baseT/Full
	Advertised pause frame use: Symmetric Receive-only
	Advertised auto-negotiation: Yes
	Advertised FEC modes: Not reported
	Link partner advertised link modes:  10baseT/Half 10baseT/Full
	                                     100baseT/Half 100baseT/Full
	                                     1000baseT/Full
	                                     2500baseT/Full
	Link partner advertised pause frame use: Symmetric Receive-only
	Link partner advertised auto-negotiation: No
	Link partner advertised FEC modes: Not reported
	Speed: 2500Mb/s
	Duplex: Full
	Auto-negotiation: on
	master-slave cfg: preferred slave
	master-slave status: master
	Port: Twisted Pair
	PHYAD: 1
	Transceiver: external
	MDI-X: on (auto)
	Supports Wake-on: pumbags
	Wake-on: b
        SecureOn password: 00:00:00:00:00:00
        Current message level: 0x00000137 (311)
                               drv probe link ifdown ifup tx_queued
	Link detected: yes

$ sudo ethtool enp9s0
Settings for enp9s0:
	Supported ports: [  ]
	Supported link modes:   10baseT/Half 10baseT/Full
	                        100baseT/Half 100baseT/Full
	                        1000baseT/Full
	                        2500baseT/Full
	Supported pause frame use: Symmetric Receive-only
	Supports auto-negotiation: Yes
	Supported FEC modes: Not reported
	Advertised link modes:  10baseT/Half 10baseT/Full
	                        100baseT/Half 100baseT/Full
	                        1000baseT/Full
	                        2500baseT/Full
	Advertised pause frame use: Symmetric Receive-only
	Advertised auto-negotiation: Yes
	Advertised FEC modes: Not reported
	Link partner advertised link modes:  10baseT/Half 10baseT/Full
	                                     100baseT/Half 100baseT/Full
	                                     1000baseT/Full
	                                     2500baseT/Full
	Link partner advertised pause frame use: Symmetric Receive-only
	Link partner advertised auto-negotiation: No
	Link partner advertised FEC modes: Not reported
	Speed: 2500Mb/s
	Duplex: Full
	Auto-negotiation: on
	master-slave cfg: preferred slave
	master-slave status: master
	Port: Twisted Pair
	PHYAD: 1
	Transceiver: external
	MDI-X: on (auto)
	Supports Wake-on: pumbags
	Wake-on: g
        SecureOn password: 00:00:00:00:00:00
        Current message level: 0x00000137 (311)
                               drv probe link ifdown ifup tx_queued
	Link detected: yes

$ sudo ethtool -s enp9s0 wol s
$ sudo ethtool enp9s0
Settings for enp9s0:
	Supported ports: [  ]
	Supported link modes:   10baseT/Half 10baseT/Full
	                        100baseT/Half 100baseT/Full
	                        1000baseT/Full
	                        2500baseT/Full
	Supported pause frame use: Symmetric Receive-only
	Supports auto-negotiation: Yes
	Supported FEC modes: Not reported
	Advertised link modes:  10baseT/Half 10baseT/Full
	                        100baseT/Half 100baseT/Full
	                        1000baseT/Full
	                        2500baseT/Full
	Advertised pause frame use: Symmetric Receive-only
	Advertised auto-negotiation: Yes
	Advertised FEC modes: Not reported
	Link partner advertised link modes:  10baseT/Half 10baseT/Full
	                                     100baseT/Half 100baseT/Full
	                                     1000baseT/Full
	                                     2500baseT/Full
	Link partner advertised pause frame use: Symmetric Receive-only
	Link partner advertised auto-negotiation: No
	Link partner advertised FEC modes: Not reported
	Speed: 2500Mb/s
	Duplex: Full
	Auto-negotiation: on
	master-slave cfg: preferred slave
	master-slave status: master
	Port: Twisted Pair
	PHYAD: 1
	Transceiver: external
	MDI-X: on (auto)
	Supports Wake-on: pumbags
	Wake-on: d
        SecureOn password: 00:00:00:00:00:00
        Current message level: 0x00000137 (311)
                               drv probe link ifdown ifup tx_queued
	Link detected: yes
  
Andrew Lunn Feb. 29, 2024, 3:06 p.m. UTC | #3
On Thu, Feb 29, 2024 at 08:59:20AM +0000, Raju.Lakkaraju@microchip.com wrote:
> Hi Andrew,
> 
> Thank you for review comments.
> 
> > -----Original Message-----
> > From: Andrew Lunn <andrew@lunn.ch>
> > Sent: Tuesday, February 27, 2024 7:28 AM
> > To: Raju Lakkaraju - I30499 <Raju.Lakkaraju@microchip.com>
> > Cc: netdev@vger.kernel.org; davem@davemloft.net; kuba@kernel.org; linux-
> > kernel@vger.kernel.org; Bryan Whitehead - C21958
> > <Bryan.Whitehead@microchip.com>; richardcochran@gmail.com;
> > UNGLinuxDriver <UNGLinuxDriver@microchip.com>
> > Subject: Re: [PATCH net 3/3] net: lan743x: Address problems with wake option
> > flags configuration sequences
> > 
> > EXTERNAL EMAIL: Do not click links or open attachments unless you know the
> > content is safe
> > 
> > On Mon, Feb 26, 2024 at 01:39:34PM +0530, Raju Lakkaraju wrote:
> > > Wake options handling has been reworked as follows:
> > > a. We only enable secure on magic packet when both secure and magic wol
> > >    options are requested together.
> > > b. If secure-on magic packet had been previously enabled, and a
> > subsequent
> > >    command does not include it, we add it. This was done to workaround a
> > >    problem with the 'pm-suspend' application which is unaware of secure-on
> > >    magic packet being enabled and can unintentionally disable it prior to
> > >    putting the system into suspend.
> > 
> > This seems to be in a bit of a grey area. But ethtool says:
> > 
> >            wol p|u|m|b|a|g|s|f|d...
> >                   Sets  Wake-on-LAN  options.   Not  all devices support this.
> >                   The argument to this option is a string of characters speci‐
> >                   fying which options to enable.
> >                   p   Wake on PHY activity
> >                   u   Wake on unicast messages
> >                   m   Wake on multicast messages
> >                   b   Wake on broadcast messages
> >                   a   Wake on ARP
> >                   g   Wake on MagicPacket™
> >                   s   Enable SecureOn™ password for MagicPacket™
> >                   f   Wake on filter(s)
> >                   d   Disable (wake on  nothing).   This  option
> >                       clears all previous options.
> > 
> > d clears everything. All other things enable one option. There does not
> > appear to be a way to disable a single option, and i would say, adding options
> > is incremental.
> > 
> 
> Yes. "d" clears everything.
> But, if we enable "g" then enable "a", "g" option overwritten by "a"

This is where i say it is a bit of a grey area. For me, they are
incremental. You can enable a and then later enable g, and you should
have both enabled.

> Please find the attached log information 
> > So:
> > 
> > > a. We only enable secure on magic packet when both secure and magic wol
> > >    options are requested together.
> > 
> > I don't think they need to be requested together. I think you can first enable
> > Wake on MagicPacket and then in a second call to ethtool Enable SecureOn
> > password for MagicPacket. I also don't think it would unreasonable to accept
> > Enable SecureOn password for MagicPacket and have that imply Wake on
> > MagicPacket.
> > 
> 
> If we need to enable any 2 options, we have to provide both options together.
> i.e.
> sudo ethtool -s enp9s0 wol ga

which i think is wrong. A driver should allow incremental adding WoL
options.

> 
> > And:
> > 
> > > b. If secure-on magic packet had been previously enabled, and a
> > subsequent
> > >    command does not include it, we add it.
> > 
> > If there has not been a d, secure-on magic packet should remain enabled until
> > there is a d.
> > 
> 
> This is not happened with existing "Ethtool".
> Please find the log information in an attachment file.

That could just be a driver bug.

Take a step back. Is there any clear documentation about how ethtool
-s wol should really work? Any comments in the code? Any man page
documentation.

Lets first understand how it is expected to work. Then we can decided
if the driver is implementing it correctly.

   Andrew
  
Raju Lakkaraju March 1, 2024, 10:22 a.m. UTC | #4
Hi Andrew,

> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Thursday, February 29, 2024 8:36 PM
> To: Raju Lakkaraju - I30499 <Raju.Lakkaraju@microchip.com>
> Cc: netdev@vger.kernel.org; davem@davemloft.net; kuba@kernel.org; linux-
> kernel@vger.kernel.org; Bryan Whitehead - C21958
> <Bryan.Whitehead@microchip.com>; richardcochran@gmail.com;
> UNGLinuxDriver <UNGLinuxDriver@microchip.com>
> Subject: Re: [PATCH net 3/3] net: lan743x: Address problems with wake option
> flags configuration sequences
> 
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the
> content is safe
> 
> On Thu, Feb 29, 2024 at 08:59:20AM +0000, Raju.Lakkaraju@microchip.com
> wrote:
> > Hi Andrew,
> >
> > Thank you for review comments.
> >
> > > -----Original Message-----
> > > From: Andrew Lunn <andrew@lunn.ch>
> > > Sent: Tuesday, February 27, 2024 7:28 AM
> > > To: Raju Lakkaraju - I30499 <Raju.Lakkaraju@microchip.com>
> > > Cc: netdev@vger.kernel.org; davem@davemloft.net; kuba@kernel.org;
> > > linux- kernel@vger.kernel.org; Bryan Whitehead - C21958
> > > <Bryan.Whitehead@microchip.com>; richardcochran@gmail.com;
> > > UNGLinuxDriver <UNGLinuxDriver@microchip.com>
> > > Subject: Re: [PATCH net 3/3] net: lan743x: Address problems with
> > > wake option flags configuration sequences
> > >
> > > EXTERNAL EMAIL: Do not click links or open attachments unless you
> > > know the content is safe
> > >
> > > On Mon, Feb 26, 2024 at 01:39:34PM +0530, Raju Lakkaraju wrote:
> > > > Wake options handling has been reworked as follows:
> > > > a. We only enable secure on magic packet when both secure and magic
> wol
> > > >    options are requested together.
> > > > b. If secure-on magic packet had been previously enabled, and a
> > > subsequent
> > > >    command does not include it, we add it. This was done to workaround
> a
> > > >    problem with the 'pm-suspend' application which is unaware of
> secure-on
> > > >    magic packet being enabled and can unintentionally disable it prior to
> > > >    putting the system into suspend.
> > >
> > > This seems to be in a bit of a grey area. But ethtool says:
> > >
> > >            wol p|u|m|b|a|g|s|f|d...
> > >                   Sets  Wake-on-LAN  options.   Not  all devices support this.
> > >                   The argument to this option is a string of characters speci‐
> > >                   fying which options to enable.
> > >                   p   Wake on PHY activity
> > >                   u   Wake on unicast messages
> > >                   m   Wake on multicast messages
> > >                   b   Wake on broadcast messages
> > >                   a   Wake on ARP
> > >                   g   Wake on MagicPacket™
> > >                   s   Enable SecureOn™ password for MagicPacket™
> > >                   f   Wake on filter(s)
> > >                   d   Disable (wake on  nothing).   This  option
> > >                       clears all previous options.
> > >
> > > d clears everything. All other things enable one option. There does
> > > not appear to be a way to disable a single option, and i would say,
> > > adding options is incremental.
> > >
> >
> > Yes. "d" clears everything.
> > But, if we enable "g" then enable "a", "g" option overwritten by "a"
> 
> This is where i say it is a bit of a grey area. For me, they are incremental. You
> can enable a and then later enable g, and you should have both enabled.
> 

Yes. But, it's "Ethtool" issue.
Even though ethtool get the WOL configuration before to set, over written with wanted wol request configuration.

> > Please find the attached log information
> > > So:
> > >
> > > > a. We only enable secure on magic packet when both secure and magic
> wol
> > > >    options are requested together.
> > >
> > > I don't think they need to be requested together. I think you can
> > > first enable Wake on MagicPacket and then in a second call to
> > > ethtool Enable SecureOn password for MagicPacket. I also don't think
> > > it would unreasonable to accept Enable SecureOn password for
> > > MagicPacket and have that imply Wake on MagicPacket.
> > >
> >
> > If we need to enable any 2 options, we have to provide both options
> together.
> > i.e.
> > sudo ethtool -s enp9s0 wol ga
> 
> which i think is wrong. A driver should allow incremental adding WoL options.
> 

Ok.

> >
> > > And:
> > >
> > > > b. If secure-on magic packet had been previously enabled, and a
> > > subsequent
> > > >    command does not include it, we add it.
> > >
> > > If there has not been a d, secure-on magic packet should remain
> > > enabled until there is a d.
> > >
> >
> > This is not happened with existing "Ethtool".
> > Please find the log information in an attachment file.
> 
> That could just be a driver bug.
> 
> Take a step back. Is there any clear documentation about how ethtool -s wol
> should really work? Any comments in the code? Any man page
> documentation.
> 

I'm not able to find  any more "ethtool" documentations other than ethtool man page.

I have gone through the ethtool application code.
(git://git.kernel.org/pub/scm/network/ethtool/ethtool.git) and check the WOL options.

In ioctl method (Not netlink), do_sset( ) function, before set the parameters, first get the WOL configuration.
(i.e. in ethtool.c file, Line no. 3294)

Then, assign/overwrite WOL wanted config to wolopts parameter.

Existing Code:  From Line 3290 -  3312
#######################################################################
3290         if (gwol_changed) {                                                     
3291                 struct ethtool_wolinfo wol;                                     
3292                                                                                 
3293                 wol.cmd = ETHTOOL_GWOL;                                         
3294                 err = send_ioctl(ctx, &wol);                                    
3295                 if (err < 0) {                                                  
3296                         perror("Cannot get current wake-on-lan settings");      
3297                 } else {                                                        
3298                         /* Change everything the user specified. */             
3299                         if (wol_change)                                         
3300                                 wol.wolopts = wol_wanted;                       
3301                         if (sopass_change) {                                    
3302                                 int i;                                          
3303                                 for (i = 0; i < SOPASS_MAX; i++)                
3304                                         wol.sopass[i] = sopass_wanted[i];       
3305                         }                                                       
3306                                                                                 
3307                         /* Try to perform the update. */                        
3308                         wol.cmd = ETHTOOL_SWOL;                                 
3309                         err = send_ioctl(ctx, &wol);                            
3310                         if (err < 0)                                            
3311                                 perror("Cannot set new wake-on-lan settings");  
3312                 }
#######################################################################
Current issue is overwriting wolopts with wol_wanted
i.e.
3300                                 wol.wolopts = wol_wanted;                       

Instead of over write, Need to "OR" the wolopts and wol_wanted
i.e.
3300                                 wol.wolopts |= wol_wanted
 
Final code changes should be:
#######################################################################
3290         if (gwol_changed) {                                                     
3291                 struct ethtool_wolinfo wol;                                     
3292                                                                                 
3293                 wol.cmd = ETHTOOL_GWOL;                                         
3294                 err = send_ioctl(ctx, &wol);                                    
3295                 if (err < 0) {                                                  
3296                         perror("Cannot get current wake-on-lan settings");      
3297                 } else {                                                        
3298                         /* Change everything the user specified. */             
3299                         if (wol_change  && wol_wanted)                                         
3300                                 wol.wolopts |= wol_wanted;
		     else if (wol_change  && !wol_wanted)
			wol.wolopts = 0                       
3301                         if (sopass_change) {                                    
3302                                 int i;                                          
3303                                 for (i = 0; i < SOPASS_MAX; i++)                
3304                                         wol.sopass[i] = sopass_wanted[i];       
3305                         }                                                       
3306                                                                                 
3307                         /* Try to perform the update. */                        
3308                         wol.cmd = ETHTOOL_SWOL;                                 
3309                         err = send_ioctl(ctx, &wol);                            
3310                         if (err < 0)                                            
3311                                 perror("Cannot set new wake-on-lan settings");  
3312                 }
#######################################################################

Similar changes require in netlink functions also. 

> Lets first understand how it is expected to work. Then we can decided if the
> driver is implementing it correctly.
> 
>    Andrew

Please find the "ethtool" application code main file as attachment file.

Thanks,
Raju
/*
 * ethtool.c: Linux ethernet device configuration tool.
 *
 * Copyright (C) 1998 David S. Miller (davem@dm.cobaltmicro.com)
 * Portions Copyright 2001 Sun Microsystems
 * Kernel 2.4 update Copyright 2001 Jeff Garzik <jgarzik@mandrakesoft.com>
 * Wake-on-LAN,natsemi,misc support by Tim Hockin <thockin@sun.com>
 * Portions Copyright 2002 Intel
 * Portions Copyright (C) Sun Microsystems 2008
 * do_test support by Eli Kupermann <eli.kupermann@intel.com>
 * ETHTOOL_PHYS_ID support by Chris Leech <christopher.leech@intel.com>
 * e1000 support by Scott Feldman <scott.feldman@intel.com>
 * e100 support by Wen Tao <wen-hwa.tao@intel.com>
 * ixgb support by Nicholas Nunley <Nicholas.d.nunley@intel.com>
 * amd8111e support by Reeja John <reeja.john@amd.com>
 * long arguments by Andi Kleen.
 * SMSC LAN911x support by Steve Glendinning <steve.glendinning@smsc.com>
 * Rx Network Flow Control configuration support <santwona.behera@sun.com>
 * Various features by Ben Hutchings <bhutchings@solarflare.com>;
 *	Copyright 2009, 2010 Solarflare Communications
 * MDI-X set support by Jesse Brandeburg <jesse.brandeburg@intel.com>
 *	Copyright 2012 Intel Corporation
 * vmxnet3 support by Shrikrishna Khare <skhare@vmware.com>
 * Various features by Ben Hutchings <ben@decadent.org.uk>;
 *	Copyright 2008-2010, 2013-2016 Ben Hutchings
 * QSFP+/QSFP28 DOM support by Vidya Sagar Ravipati <vidya@cumulusnetworks.com>
 *
 * TODO:
 *   * show settings for all devices
 */

#include "internal.h"
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include <errno.h>
#include <sys/utsname.h>
#include <limits.h>
#include <ctype.h>
#include <inttypes.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <linux/ioctl.h>
#include <linux/sockios.h>
#include <linux/netlink.h>

#include "common.h"
#include "netlink/extapi.h"

#ifndef MAX_ADDR_LEN
#define MAX_ADDR_LEN	32
#endif

#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))

static void exit_bad_args(void) __attribute__((noreturn));

static void exit_bad_args(void)
{
	fprintf(stderr,
		"ethtool: bad command line argument(s)\n"
		"For more information run ethtool -h\n");
	exit(1);
}

static void exit_nlonly_param(const char *name) __attribute__((noreturn));

static void exit_nlonly_param(const char *name)
{
	fprintf(stderr,
		"ethtool: parameter '%s' can be used only with netlink\n",
		name);
	exit(1);
}

typedef enum {
	CMDL_NONE,
	CMDL_BOOL,
	CMDL_S32,
	CMDL_U8,
	CMDL_U16,
	CMDL_U32,
	CMDL_U64,
	CMDL_BE16,
	CMDL_IP4,
	CMDL_STR,
	CMDL_FLAG,
	CMDL_MAC,
} cmdline_type_t;

struct cmdline_info {
	const char *name;
	cmdline_type_t type;
	/* Points to int (BOOL), s32, u16, u32 (U32/FLAG/IP4), u64,
	 * char * (STR) or u8[6] (MAC).  For FLAG, the value accumulates
	 * all flags to be set. */
	void *wanted_val;
	void *ioctl_val;
	/* For FLAG, the flag value to be set/cleared */
	u32 flag_val;
	/* For FLAG, points to u32 and accumulates all flags seen.
	 * For anything else, points to int and is set if the option is
	 * seen. */
	void *seen_val;
};

struct feature_def {
	char name[ETH_GSTRING_LEN];
	int off_flag_index; /* index in off_flag_def; negative if none match */
};

struct feature_defs {
	size_t n_features;
	/* Number of features each offload flag is associated with */
	unsigned int off_flag_matched[OFF_FLAG_DEF_SIZE];
	/* Name and offload flag index for each feature */
	struct feature_def def[0];
};

#define FEATURE_BITS_TO_BLOCKS(n_bits)		DIV_ROUND_UP(n_bits, 32U)
#define FEATURE_WORD(blocks, index, field)	((blocks)[(index) / 32U].field)
#define FEATURE_FIELD_FLAG(index)		(1U << (index) % 32U)
#define FEATURE_BIT_SET(blocks, index, field)			\
	(FEATURE_WORD(blocks, index, field) |= FEATURE_FIELD_FLAG(index))
#define FEATURE_BIT_CLEAR(blocks, index, field)			\
	(FEATURE_WORD(blocks, index, filed) &= ~FEATURE_FIELD_FLAG(index))
#define FEATURE_BIT_IS_SET(blocks, index, field)		\
	(FEATURE_WORD(blocks, index, field) & FEATURE_FIELD_FLAG(index))

static long long
get_int_range(char *str, int base, long long min, long long max)
{
	long long v;
	char *endp;

	if (!str)
		exit_bad_args();
	errno = 0;
	v = strtoll(str, &endp, base);
	if (errno || *endp || v < min || v > max)
		exit_bad_args();
	return v;
}

static unsigned long long
get_uint_range(char *str, int base, unsigned long long max)
{
	unsigned long long v;
	char *endp;

	if (!str)
		exit_bad_args();
	errno = 0;
	v = strtoull(str, &endp, base);
	if (errno || *endp || v > max)
		exit_bad_args();
	return v;
}

static int get_int(char *str, int base)
{
	return get_int_range(str, base, INT_MIN, INT_MAX);
}

static u32 get_u32(char *str, int base)
{
	return get_uint_range(str, base, 0xffffffff);
}

static void get_mac_addr(char *src, unsigned char *dest)
{
	int count;
	int i;
	int buf[ETH_ALEN];

	count = sscanf(src, "%2x:%2x:%2x:%2x:%2x:%2x",
		&buf[0], &buf[1], &buf[2], &buf[3], &buf[4], &buf[5]);
	if (count != ETH_ALEN)
		exit_bad_args();

	for (i = 0; i < count; i++)
		dest[i] = buf[i];
}

static int parse_hex_u32_bitmap(const char *s,
				unsigned int nbits, u32 *result)
{
	const unsigned int nwords = __KERNEL_DIV_ROUND_UP(nbits, 32);
	size_t slen = strlen(s);
	size_t i;

	/* ignore optional '0x' prefix */
	if ((slen > 2) && (strncasecmp(s, "0x", 2) == 0)) {
		slen -= 2;
		s += 2;
	}

	if (slen > 8 * nwords)  /* up to 2 digits per byte */
		return -1;

	memset(result, 0, 4 * nwords);
	for (i = 0; i < slen; ++i) {
		const unsigned int shift = (slen - 1 - i) * 4;
		u32 *dest = &result[shift / 32];
		u32 nibble;

		if ('a' <= s[i] && s[i] <= 'f')
			nibble = 0xa + (s[i] - 'a');
		else if ('A' <= s[i] && s[i] <= 'F')
			nibble = 0xa + (s[i] - 'A');
		else if ('0' <= s[i] && s[i] <= '9')
			nibble = (s[i] - '0');
		else
			return -1;

		*dest |= (nibble << (shift % 32));
	}

	return 0;
}

static void parse_generic_cmdline(struct cmd_context *ctx,
				  int *changed,
				  struct cmdline_info *info,
				  unsigned int n_info)
{
	unsigned int argc = ctx->argc;
	char **argp = ctx->argp;
	unsigned int i, idx;
	int found;

	for (i = 0; i < argc; i++) {
		found = 0;
		for (idx = 0; idx < n_info; idx++) {
			if (!strcmp(info[idx].name, argp[i])) {
				found = 1;
				*changed = 1;
				if (info[idx].type != CMDL_FLAG &&
				    info[idx].seen_val)
					*(int *)info[idx].seen_val = 1;
				i += 1;
				if (i >= argc)
					exit_bad_args();
				switch (info[idx].type) {
				case CMDL_BOOL: {
					int *p = info[idx].wanted_val;
					if (!strcmp(argp[i], "on"))
						*p = 1;
					else if (!strcmp(argp[i], "off"))
						*p = 0;
					else
						exit_bad_args();
					break;
				}
				case CMDL_S32: {
					s32 *p = info[idx].wanted_val;
					*p = get_int_range(argp[i], 0,
							   -0x80000000LL,
							   0x7fffffff);
					break;
				}
				case CMDL_U8: {
					u8 *p = info[idx].wanted_val;
					*p = get_uint_range(argp[i], 0, 0xff);
					break;
				}
				case CMDL_U16: {
					u16 *p = info[idx].wanted_val;
					*p = get_uint_range(argp[i], 0, 0xffff);
					break;
				}
				case CMDL_U32: {
					u32 *p = info[idx].wanted_val;
					*p = get_uint_range(argp[i], 0,
							    0xffffffff);
					break;
				}
				case CMDL_U64: {
					u64 *p = info[idx].wanted_val;
					*p = get_uint_range(
						argp[i], 0,
						0xffffffffffffffffLL);
					break;
				}
				case CMDL_BE16: {
					u16 *p = info[idx].wanted_val;
					*p = cpu_to_be16(
						get_uint_range(argp[i], 0,
							       0xffff));
					break;
				}
				case CMDL_IP4: {
					u32 *p = info[idx].wanted_val;
					struct in_addr in;
					if (!inet_pton(AF_INET, argp[i], &in))
						exit_bad_args();
					*p = in.s_addr;
					break;
				}
				case CMDL_MAC:
					get_mac_addr(argp[i],
						     info[idx].wanted_val);
					break;
				case CMDL_FLAG: {
					u32 *p;
					p = info[idx].seen_val;
					*p |= info[idx].flag_val;
					if (!strcmp(argp[i], "on")) {
						p = info[idx].wanted_val;
						*p |= info[idx].flag_val;
					} else if (strcmp(argp[i], "off")) {
						exit_bad_args();
					}
					break;
				}
				case CMDL_STR: {
					char **s = info[idx].wanted_val;
					*s = strdup(argp[i]);
					break;
				}
				default:
					exit_bad_args();
				}
				break;
			}
		}
		if (!found)
			exit_bad_args();
	}
}

static void flag_to_cmdline_info(const char *name, u32 value,
				 u32 *wanted, u32 *mask,
				 struct cmdline_info *cli)
{
	memset(cli, 0, sizeof(*cli));
	cli->name = name;
	cli->type = CMDL_FLAG;
	cli->flag_val = value;
	cli->wanted_val = wanted;
	cli->seen_val = mask;
}

static int rxflow_str_to_type(const char *str)
{
	int flow_type = 0;

	if (!strcmp(str, "tcp4"))
		flow_type = TCP_V4_FLOW;
	else if (!strcmp(str, "udp4"))
		flow_type = UDP_V4_FLOW;
	else if (!strcmp(str, "ah4") || !strcmp(str, "esp4"))
		flow_type = AH_ESP_V4_FLOW;
	else if (!strcmp(str, "sctp4"))
		flow_type = SCTP_V4_FLOW;
	else if (!strcmp(str, "tcp6"))
		flow_type = TCP_V6_FLOW;
	else if (!strcmp(str, "udp6"))
		flow_type = UDP_V6_FLOW;
	else if (!strcmp(str, "ah6") || !strcmp(str, "esp6"))
		flow_type = AH_ESP_V6_FLOW;
	else if (!strcmp(str, "sctp6"))
		flow_type = SCTP_V6_FLOW;
	else if (!strcmp(str, "ether"))
		flow_type = ETHER_FLOW;

	return flow_type;
}

static int do_version(struct cmd_context *ctx __maybe_unused)
{
	fprintf(stdout,
		PACKAGE " version " VERSION
#ifndef ETHTOOL_ENABLE_PRETTY_DUMP
		" (pretty dumps disabled)"
#endif
		"\n");
	return 0;
}

/* link mode routines */

static ETHTOOL_DECLARE_LINK_MODE_MASK(all_advertised_modes);
static ETHTOOL_DECLARE_LINK_MODE_MASK(all_advertised_flags);

static void init_global_link_mode_masks(void)
{
	static const enum ethtool_link_mode_bit_indices
		all_advertised_modes_bits[] = {
		ETHTOOL_LINK_MODE_10baseT_Half_BIT,
		ETHTOOL_LINK_MODE_10baseT_Full_BIT,
		ETHTOOL_LINK_MODE_100baseT_Half_BIT,
		ETHTOOL_LINK_MODE_100baseT_Full_BIT,
		ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
		ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
		ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
		ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
		ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
		ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
		ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
		ETHTOOL_LINK_MODE_10000baseR_FEC_BIT,
		ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT,
		ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT,
		ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT,
		ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT,
		ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT,
		ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT,
		ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT,
		ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT,
		ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT,
		ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT,
		ETHTOOL_LINK_MODE_25000baseCR_Full_BIT,
		ETHTOOL_LINK_MODE_25000baseKR_Full_BIT,
		ETHTOOL_LINK_MODE_25000baseSR_Full_BIT,
		ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT,
		ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT,
		ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT,
		ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT,
		ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT,
		ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT,
		ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT,
		ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
		ETHTOOL_LINK_MODE_10000baseCR_Full_BIT,
		ETHTOOL_LINK_MODE_10000baseSR_Full_BIT,
		ETHTOOL_LINK_MODE_10000baseLR_Full_BIT,
		ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT,
		ETHTOOL_LINK_MODE_10000baseER_Full_BIT,
		ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
		ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
		ETHTOOL_LINK_MODE_50000baseKR_Full_BIT,
		ETHTOOL_LINK_MODE_50000baseSR_Full_BIT,
		ETHTOOL_LINK_MODE_50000baseCR_Full_BIT,
		ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT,
		ETHTOOL_LINK_MODE_50000baseDR_Full_BIT,
		ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT,
		ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT,
		ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT,
		ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT,
		ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT,
		ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT,
		ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT,
		ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT,
		ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT,
		ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT,
		ETHTOOL_LINK_MODE_100baseT1_Full_BIT,
		ETHTOOL_LINK_MODE_1000baseT1_Full_BIT,
		ETHTOOL_LINK_MODE_400000baseKR8_Full_BIT,
		ETHTOOL_LINK_MODE_400000baseSR8_Full_BIT,
		ETHTOOL_LINK_MODE_400000baseLR8_ER8_FR8_Full_BIT,
		ETHTOOL_LINK_MODE_400000baseDR8_Full_BIT,
		ETHTOOL_LINK_MODE_400000baseCR8_Full_BIT,
		ETHTOOL_LINK_MODE_100000baseKR_Full_BIT,
		ETHTOOL_LINK_MODE_100000baseSR_Full_BIT,
		ETHTOOL_LINK_MODE_100000baseLR_ER_FR_Full_BIT,
		ETHTOOL_LINK_MODE_100000baseCR_Full_BIT,
		ETHTOOL_LINK_MODE_100000baseDR_Full_BIT,
		ETHTOOL_LINK_MODE_200000baseKR2_Full_BIT,
		ETHTOOL_LINK_MODE_200000baseSR2_Full_BIT,
		ETHTOOL_LINK_MODE_200000baseLR2_ER2_FR2_Full_BIT,
		ETHTOOL_LINK_MODE_200000baseDR2_Full_BIT,
		ETHTOOL_LINK_MODE_200000baseCR2_Full_BIT,
		ETHTOOL_LINK_MODE_400000baseKR4_Full_BIT,
		ETHTOOL_LINK_MODE_400000baseSR4_Full_BIT,
		ETHTOOL_LINK_MODE_400000baseLR4_ER4_FR4_Full_BIT,
		ETHTOOL_LINK_MODE_400000baseDR4_Full_BIT,
		ETHTOOL_LINK_MODE_400000baseCR4_Full_BIT,
		ETHTOOL_LINK_MODE_100baseFX_Half_BIT,
		ETHTOOL_LINK_MODE_100baseFX_Full_BIT,
		ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
		ETHTOOL_LINK_MODE_800000baseCR8_Full_BIT,
		ETHTOOL_LINK_MODE_800000baseKR8_Full_BIT,
		ETHTOOL_LINK_MODE_800000baseDR8_Full_BIT,
		ETHTOOL_LINK_MODE_800000baseDR8_2_Full_BIT,
		ETHTOOL_LINK_MODE_800000baseSR8_Full_BIT,
		ETHTOOL_LINK_MODE_800000baseVR8_Full_BIT,
		ETHTOOL_LINK_MODE_10baseT1S_Full_BIT,
		ETHTOOL_LINK_MODE_10baseT1S_Half_BIT,
		ETHTOOL_LINK_MODE_10baseT1S_P2MP_Half_BIT,
	};
	static const enum ethtool_link_mode_bit_indices
		additional_advertised_flags_bits[] = {
		ETHTOOL_LINK_MODE_Autoneg_BIT,
		ETHTOOL_LINK_MODE_TP_BIT,
		ETHTOOL_LINK_MODE_AUI_BIT,
		ETHTOOL_LINK_MODE_MII_BIT,
		ETHTOOL_LINK_MODE_FIBRE_BIT,
		ETHTOOL_LINK_MODE_BNC_BIT,
		ETHTOOL_LINK_MODE_Pause_BIT,
		ETHTOOL_LINK_MODE_Asym_Pause_BIT,
		ETHTOOL_LINK_MODE_Backplane_BIT,
		ETHTOOL_LINK_MODE_FEC_NONE_BIT,
		ETHTOOL_LINK_MODE_FEC_RS_BIT,
		ETHTOOL_LINK_MODE_FEC_BASER_BIT,
		ETHTOOL_LINK_MODE_FEC_LLRS_BIT,
	};
	unsigned int i;

	ethtool_link_mode_zero(all_advertised_modes);
	ethtool_link_mode_zero(all_advertised_flags);
	for (i = 0; i < ARRAY_SIZE(all_advertised_modes_bits); ++i) {
		ethtool_link_mode_set_bit(all_advertised_modes_bits[i],
					  all_advertised_modes);
		ethtool_link_mode_set_bit(all_advertised_modes_bits[i],
					  all_advertised_flags);
	}

	for (i = 0; i < ARRAY_SIZE(additional_advertised_flags_bits); ++i) {
		ethtool_link_mode_set_bit(
			additional_advertised_flags_bits[i],
			all_advertised_flags);
	}
}

static void dump_link_caps(const char *prefix, const char *an_prefix,
			   const u32 *mask, int link_mode_only);

static void dump_supported(const struct ethtool_link_usettings *link_usettings)
{
	fprintf(stdout, "	Supported ports: [ ");
	if (ethtool_link_mode_test_bit(
		    ETHTOOL_LINK_MODE_TP_BIT,
		    link_usettings->link_modes.supported))
		fprintf(stdout, "TP ");
	if (ethtool_link_mode_test_bit(
		    ETHTOOL_LINK_MODE_AUI_BIT,
		    link_usettings->link_modes.supported))
		fprintf(stdout, "AUI ");
	if (ethtool_link_mode_test_bit(
		    ETHTOOL_LINK_MODE_BNC_BIT,
		    link_usettings->link_modes.supported))
		fprintf(stdout, "BNC ");
	if (ethtool_link_mode_test_bit(
		    ETHTOOL_LINK_MODE_MII_BIT,
		    link_usettings->link_modes.supported))
		fprintf(stdout, "MII ");
	if (ethtool_link_mode_test_bit(
		    ETHTOOL_LINK_MODE_FIBRE_BIT,
		    link_usettings->link_modes.supported))
		fprintf(stdout, "FIBRE ");
	if (ethtool_link_mode_test_bit(
		    ETHTOOL_LINK_MODE_Backplane_BIT,
		    link_usettings->link_modes.supported))
		fprintf(stdout, "Backplane ");
	fprintf(stdout, "]\n");

	dump_link_caps("Supported", "Supports",
		       link_usettings->link_modes.supported, 0);
}

/* Print link capability flags (supported, advertised or lp_advertised).
 * Assumes that the corresponding SUPPORTED and ADVERTISED flags are equal.
 */
static void dump_link_caps(const char *prefix, const char *an_prefix,
			   const u32 *mask, int link_mode_only)
{
	static const struct {
		int same_line; /* print on same line as previous */
		unsigned int bit_index;
		const char *name;
	} mode_defs[] = {
		{ 0, ETHTOOL_LINK_MODE_10baseT_Half_BIT,
		  "10baseT/Half" },
		{ 1, ETHTOOL_LINK_MODE_10baseT_Full_BIT,
		  "10baseT/Full" },
		{ 0, ETHTOOL_LINK_MODE_100baseT_Half_BIT,
		  "100baseT/Half" },
		{ 1, ETHTOOL_LINK_MODE_100baseT_Full_BIT,
		  "100baseT/Full" },
		{ 0, ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
		  "1000baseT/Half" },
		{ 1, ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
		  "1000baseT/Full" },
		{ 0, ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
		  "10000baseT/Full" },
		{ 0, ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
		  "2500baseX/Full" },
		{ 0, ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
		  "1000baseKX/Full" },
		{ 0, ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
		  "10000baseKX4/Full" },
		{ 0, ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
		  "10000baseKR/Full" },
		{ 0, ETHTOOL_LINK_MODE_10000baseR_FEC_BIT,
		  "10000baseR_FEC" },
		{ 0, ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT,
		  "20000baseMLD2/Full" },
		{ 0, ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT,
		  "20000baseKR2/Full" },
		{ 0, ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT,
		  "40000baseKR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT,
		  "40000baseCR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT,
		  "40000baseSR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT,
		  "40000baseLR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT,
		  "56000baseKR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT,
		  "56000baseCR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT,
		  "56000baseSR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT,
		  "56000baseLR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_25000baseCR_Full_BIT,
		  "25000baseCR/Full" },
		{ 0, ETHTOOL_LINK_MODE_25000baseKR_Full_BIT,
		  "25000baseKR/Full" },
		{ 0, ETHTOOL_LINK_MODE_25000baseSR_Full_BIT,
		  "25000baseSR/Full" },
		{ 0, ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT,
		  "50000baseCR2/Full" },
		{ 0, ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT,
		  "50000baseKR2/Full" },
		{ 0, ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT,
		  "100000baseKR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT,
		  "100000baseSR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT,
		  "100000baseCR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT,
		  "100000baseLR4_ER4/Full" },
		{ 0, ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT,
		  "50000baseSR2/Full" },
		{ 0, ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
		  "1000baseX/Full" },
		{ 0, ETHTOOL_LINK_MODE_10000baseCR_Full_BIT,
		  "10000baseCR/Full" },
		{ 0, ETHTOOL_LINK_MODE_10000baseSR_Full_BIT,
		  "10000baseSR/Full" },
		{ 0, ETHTOOL_LINK_MODE_10000baseLR_Full_BIT,
		  "10000baseLR/Full" },
		{ 0, ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT,
		  "10000baseLRM/Full" },
		{ 0, ETHTOOL_LINK_MODE_10000baseER_Full_BIT,
		  "10000baseER/Full" },
		{ 0, ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
		  "2500baseT/Full" },
		{ 0, ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
		  "5000baseT/Full" },
		{ 0, ETHTOOL_LINK_MODE_50000baseKR_Full_BIT,
		  "50000baseKR/Full" },
		{ 0, ETHTOOL_LINK_MODE_50000baseSR_Full_BIT,
		  "50000baseSR/Full" },
		{ 0, ETHTOOL_LINK_MODE_50000baseCR_Full_BIT,
		  "50000baseCR/Full" },
		{ 0, ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT,
		  "50000baseLR_ER_FR/Full" },
		{ 0, ETHTOOL_LINK_MODE_50000baseDR_Full_BIT,
		  "50000baseDR/Full" },
		{ 0, ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT,
		  "100000baseKR2/Full" },
		{ 0, ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT,
		  "100000baseSR2/Full" },
		{ 0, ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT,
		  "100000baseCR2/Full" },
		{ 0, ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT,
		  "100000baseLR2_ER2_FR2/Full" },
		{ 0, ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT,
		  "100000baseDR2/Full" },
		{ 0, ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT,
		  "200000baseKR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT,
		  "200000baseSR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT,
		  "200000baseLR4_ER4_FR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT,
		  "200000baseDR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT,
		  "200000baseCR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_100baseT1_Full_BIT,
		  "100baseT1/Full" },
		{ 0, ETHTOOL_LINK_MODE_1000baseT1_Full_BIT,
		  "1000baseT1/Full" },
		{ 0, ETHTOOL_LINK_MODE_400000baseKR8_Full_BIT,
		  "400000baseKR8/Full" },
		{ 0, ETHTOOL_LINK_MODE_400000baseSR8_Full_BIT,
		  "400000baseSR8/Full" },
		{ 0, ETHTOOL_LINK_MODE_400000baseLR8_ER8_FR8_Full_BIT,
		  "400000baseLR8_ER8_FR8/Full" },
		{ 0, ETHTOOL_LINK_MODE_400000baseDR8_Full_BIT,
		  "400000baseDR8/Full" },
		{ 0, ETHTOOL_LINK_MODE_400000baseCR8_Full_BIT,
		  "400000baseCR8/Full" },
		{ 0, ETHTOOL_LINK_MODE_100000baseKR_Full_BIT,
		  "100000baseKR/Full" },
		{ 0, ETHTOOL_LINK_MODE_100000baseSR_Full_BIT,
		  "100000baseSR/Full" },
		{ 0, ETHTOOL_LINK_MODE_100000baseLR_ER_FR_Full_BIT,
		  "100000baseLR_ER_FR/Full" },
		{ 0, ETHTOOL_LINK_MODE_100000baseDR_Full_BIT,
		  "100000baseDR/Full" },
		{ 0, ETHTOOL_LINK_MODE_100000baseCR_Full_BIT,
		  "100000baseCR/Full" },
		{ 0, ETHTOOL_LINK_MODE_200000baseKR2_Full_BIT,
		  "200000baseKR2/Full" },
		{ 0, ETHTOOL_LINK_MODE_200000baseSR2_Full_BIT,
		  "200000baseSR2/Full" },
		{ 0, ETHTOOL_LINK_MODE_200000baseLR2_ER2_FR2_Full_BIT,
		  "200000baseLR2_ER2_FR2/Full" },
		{ 0, ETHTOOL_LINK_MODE_200000baseDR2_Full_BIT,
		  "200000baseDR2/Full" },
		{ 0, ETHTOOL_LINK_MODE_200000baseCR2_Full_BIT,
		  "200000baseCR2/Full" },
		{ 0, ETHTOOL_LINK_MODE_400000baseKR4_Full_BIT,
		  "400000baseKR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_400000baseSR4_Full_BIT,
		  "400000baseSR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_400000baseLR4_ER4_FR4_Full_BIT,
		  "400000baseLR4_ER4_FR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_400000baseDR4_Full_BIT,
		  "400000baseDR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_400000baseCR4_Full_BIT,
		  "400000baseCR4/Full" },
		{ 0, ETHTOOL_LINK_MODE_100baseFX_Half_BIT,
		  "100baseFX/Half" },
		{ 1, ETHTOOL_LINK_MODE_100baseFX_Full_BIT,
		  "100baseFX/Full" },
		{ 0, ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
		  "10baseT1L/Full" },
		{ 0, ETHTOOL_LINK_MODE_800000baseCR8_Full_BIT,
		  "800000baseCR8/Full" },
		{ 0, ETHTOOL_LINK_MODE_800000baseKR8_Full_BIT,
		  "800000baseKR8/Full" },
		{ 0, ETHTOOL_LINK_MODE_800000baseDR8_Full_BIT,
		  "800000baseDR8/Full" },
		{ 0, ETHTOOL_LINK_MODE_800000baseDR8_2_Full_BIT,
		  "800000baseDR8_2/Full" },
		{ 0, ETHTOOL_LINK_MODE_800000baseSR8_Full_BIT,
		  "800000baseSR8/Full" },
		{ 0, ETHTOOL_LINK_MODE_800000baseVR8_Full_BIT,
		  "800000baseVR8/Full" },
		{ 0, ETHTOOL_LINK_MODE_10baseT1S_Full_BIT,
		  "10baseT1S/Full" },
		{ 1, ETHTOOL_LINK_MODE_10baseT1S_Half_BIT,
		  "10baseT1S/Half" },
		{ 0, ETHTOOL_LINK_MODE_10baseT1S_P2MP_Half_BIT,
		  "10baseT1S/Half" },
	};
	int indent;
	int did1, new_line_pend;
	int fecreported = 0;
	unsigned int i;

	/* Indent just like the separate functions used to */
	indent = strlen(prefix) + 14;
	if (indent < 24)
		indent = 24;

	fprintf(stdout, "	%s link modes:%*s", prefix,
		indent - (int)strlen(prefix) - 12, "");
	did1 = 0;
	new_line_pend = 0;
	for (i = 0; i < ARRAY_SIZE(mode_defs); i++) {
		if (did1 && !mode_defs[i].same_line)
			new_line_pend = 1;
		if (ethtool_link_mode_test_bit(mode_defs[i].bit_index,
					       mask)) {
			if (new_line_pend) {
				fprintf(stdout, "\n");
				fprintf(stdout, "	%*s", indent, "");
				new_line_pend = 0;
			}
			did1++;
			fprintf(stdout, "%s ", mode_defs[i].name);
		}
	}
	if (did1 == 0)
		fprintf(stdout, "Not reported");
	fprintf(stdout, "\n");

	if (!link_mode_only) {
		fprintf(stdout, "	%s pause frame use: ", prefix);
		if (ethtool_link_mode_test_bit(
			    ETHTOOL_LINK_MODE_Pause_BIT, mask)) {
			fprintf(stdout, "Symmetric");
			if (ethtool_link_mode_test_bit(
				    ETHTOOL_LINK_MODE_Asym_Pause_BIT, mask))
				fprintf(stdout, " Receive-only");
			fprintf(stdout, "\n");
		} else {
			if (ethtool_link_mode_test_bit(
				    ETHTOOL_LINK_MODE_Asym_Pause_BIT, mask))
				fprintf(stdout, "Transmit-only\n");
			else
				fprintf(stdout, "No\n");
		}

		fprintf(stdout, "	%s auto-negotiation: ", an_prefix);
		if (ethtool_link_mode_test_bit(
			    ETHTOOL_LINK_MODE_Autoneg_BIT, mask))
			fprintf(stdout, "Yes\n");
		else
			fprintf(stdout, "No\n");

		fprintf(stdout, "	%s FEC modes:", prefix);
		if (ethtool_link_mode_test_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT,
					       mask)) {
			fprintf(stdout, " None");
			fecreported = 1;
		}
		if (ethtool_link_mode_test_bit(ETHTOOL_LINK_MODE_FEC_BASER_BIT,
					       mask)) {
			fprintf(stdout, " BaseR");
			fecreported = 1;
		}
		if (ethtool_link_mode_test_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT,
					       mask)) {
			fprintf(stdout, " RS");
			fecreported = 1;
		}
		if (ethtool_link_mode_test_bit(ETHTOOL_LINK_MODE_FEC_LLRS_BIT,
					       mask)) {
			fprintf(stdout, " LLRS");
			fecreported = 1;
		}

		if (!fecreported)
			fprintf(stdout, " Not reported");
		fprintf(stdout, "\n");
	}
}

static int
dump_link_usettings(const struct ethtool_link_usettings *link_usettings)
{
	dump_supported(link_usettings);
	dump_link_caps("Advertised", "Advertised",
		       link_usettings->link_modes.advertising, 0);
	if (!ethtool_link_mode_is_empty(
		    link_usettings->link_modes.lp_advertising))
		dump_link_caps("Link partner advertised",
			       "Link partner advertised",
			       link_usettings->link_modes.lp_advertising, 0);

	fprintf(stdout, "	Speed: ");
	if (link_usettings->base.speed == 0
	    || link_usettings->base.speed == (u16)(-1)
	    || link_usettings->base.speed == (u32)(-1))
		fprintf(stdout, "Unknown!\n");
	else
		fprintf(stdout, "%uMb/s\n", link_usettings->base.speed);

	fprintf(stdout, "	Duplex: ");
	switch (link_usettings->base.duplex) {
	case DUPLEX_HALF:
		fprintf(stdout, "Half\n");
		break;
	case DUPLEX_FULL:
		fprintf(stdout, "Full\n");
		break;
	default:
		fprintf(stdout, "Unknown! (%i)\n", link_usettings->base.duplex);
		break;
	};

	fprintf(stdout, "	Port: ");
	switch (link_usettings->base.port) {
	case PORT_TP:
		fprintf(stdout, "Twisted Pair\n");
		break;
	case PORT_AUI:
		fprintf(stdout, "AUI\n");
		break;
	case PORT_BNC:
		fprintf(stdout, "BNC\n");
		break;
	case PORT_MII:
		fprintf(stdout, "MII\n");
		break;
	case PORT_FIBRE:
		fprintf(stdout, "FIBRE\n");
		break;
	case PORT_DA:
		fprintf(stdout, "Direct Attach Copper\n");
		break;
	case PORT_NONE:
		fprintf(stdout, "None\n");
		break;
	case PORT_OTHER:
		fprintf(stdout, "Other\n");
		break;
	default:
		fprintf(stdout, "Unknown! (%i)\n", link_usettings->base.port);
		break;
	};

	fprintf(stdout, "	PHYAD: %d\n", link_usettings->base.phy_address);
	fprintf(stdout, "	Transceiver: ");
	switch (link_usettings->deprecated.transceiver) {
	case XCVR_INTERNAL:
		fprintf(stdout, "internal\n");
		break;
	case XCVR_EXTERNAL:
		fprintf(stdout, "external\n");
		break;
	default:
		fprintf(stdout, "Unknown!\n");
		break;
	};

	fprintf(stdout, "	Auto-negotiation: %s\n",
		(link_usettings->base.autoneg == AUTONEG_DISABLE) ?
		"off" : "on");

	if (link_usettings->base.port == PORT_TP)
		dump_mdix(link_usettings->base.eth_tp_mdix,
			  link_usettings->base.eth_tp_mdix_ctrl);

	return 0;
}

static int dump_drvinfo(struct ethtool_drvinfo *info)
{
	fprintf(stdout,
		"driver: %.*s\n"
		"version: %.*s\n"
		"firmware-version: %.*s\n"
		"expansion-rom-version: %.*s\n"
		"bus-info: %.*s\n"
		"supports-statistics: %s\n"
		"supports-test: %s\n"
		"supports-eeprom-access: %s\n"
		"supports-register-dump: %s\n"
		"supports-priv-flags: %s\n",
		(int)sizeof(info->driver), info->driver,
		(int)sizeof(info->version), info->version,
		(int)sizeof(info->fw_version), info->fw_version,
		(int)sizeof(info->erom_version), info->erom_version,
		(int)sizeof(info->bus_info), info->bus_info,
		info->n_stats ? "yes" : "no",
		info->testinfo_len ? "yes" : "no",
		info->eedump_len ? "yes" : "no",
		info->regdump_len ? "yes" : "no",
		info->n_priv_flags ? "yes" : "no");

	return 0;
}

static int parse_wolopts(char *optstr, u32 *data)
{
	*data = 0;
	while (*optstr) {
		switch (*optstr) {
		case 'p':
			*data |= WAKE_PHY;
			break;
		case 'u':
			*data |= WAKE_UCAST;
			break;
		case 'm':
			*data |= WAKE_MCAST;
			break;
		case 'b':
			*data |= WAKE_BCAST;
			break;
		case 'a':
			*data |= WAKE_ARP;
			break;
		case 'g':
			*data |= WAKE_MAGIC;
			break;
		case 's':
			*data |= WAKE_MAGICSECURE;
			break;
		case 'f':
			*data |= WAKE_FILTER;
			break;
		case 'd':
			*data = 0;
			break;
		default:
			return -1;
		}
		optstr++;
	}
	return 0;
}

static int parse_rxfhashopts(char *optstr, u32 *data)
{
	*data = 0;
	while (*optstr) {
		switch (*optstr) {
		case 'm':
			*data |= RXH_L2DA;
			break;
		case 'v':
			*data |= RXH_VLAN;
			break;
		case 't':
			*data |= RXH_L3_PROTO;
			break;
		case 's':
			*data |= RXH_IP_SRC;
			break;
		case 'd':
			*data |= RXH_IP_DST;
			break;
		case 'f':
			*data |= RXH_L4_B_0_1;
			break;
		case 'n':
			*data |= RXH_L4_B_2_3;
			break;
		case 'r':
			*data |= RXH_DISCARD;
			break;
		default:
			return -1;
		}
		optstr++;
	}
	return 0;
}

static char *unparse_rxfhashopts(u64 opts)
{
	static char buf[300];

	memset(buf, 0, sizeof(buf));

	if (opts) {
		if (opts & RXH_L2DA)
			strcat(buf, "L2DA\n");
		if (opts & RXH_VLAN)
			strcat(buf, "VLAN tag\n");
		if (opts & RXH_L3_PROTO)
			strcat(buf, "L3 proto\n");
		if (opts & RXH_IP_SRC)
			strcat(buf, "IP SA\n");
		if (opts & RXH_IP_DST)
			strcat(buf, "IP DA\n");
		if (opts & RXH_L4_B_0_1)
			strcat(buf, "L4 bytes 0 & 1 [TCP/UDP src port]\n");
		if (opts & RXH_L4_B_2_3)
			strcat(buf, "L4 bytes 2 & 3 [TCP/UDP dst port]\n");
	} else {
		sprintf(buf, "None");
	}

	return buf;
}

static int convert_string_to_hashkey(char *rss_hkey, u32 key_size,
				     const char *rss_hkey_string)
{
	u32 i = 0;
	int hex_byte, len;

	do {
		if (i > (key_size - 1)) {
			fprintf(stderr,
				"Key is too long for device (%u > %u)\n",
				i + 1, key_size);
			goto err;
		}

		if (sscanf(rss_hkey_string, "%2x%n", &hex_byte, &len) < 1 ||
		    len != 2) {
			fprintf(stderr, "Invalid RSS hash key format\n");
			goto err;
		}

		rss_hkey[i++] = hex_byte;
		rss_hkey_string += 2;

		if (*rss_hkey_string == ':') {
			rss_hkey_string++;
		} else if (*rss_hkey_string != '\0') {
			fprintf(stderr, "Invalid RSS hash key format\n");
			goto err;
		}

	} while (*rss_hkey_string);

	if (i != key_size) {
		fprintf(stderr, "Key is too short for device (%u < %u)\n",
			i, key_size);
		goto err;
	}

	return 0;
err:
	return 2;
}

static int parse_hkey(char **rss_hkey, u32 key_size,
		      const char *rss_hkey_string)
{
	if (!key_size) {
		fprintf(stderr,
			"Cannot set RX flow hash configuration:\n"
			" Hash key setting not supported\n");
		return 1;
	}

	*rss_hkey = malloc(key_size);
	if (!(*rss_hkey)) {
		perror("Cannot allocate memory for RSS hash key");
		return 1;
	}

	if (convert_string_to_hashkey(*rss_hkey, key_size,
				      rss_hkey_string)) {
		free(*rss_hkey);
		*rss_hkey = NULL;
		return 2;
	}
	return 0;
}

#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
static const struct {
	const char *name;
	int (*func)(struct ethtool_drvinfo *info, struct ethtool_regs *regs);

} driver_list[] = {
	{ "8139cp", realtek_dump_regs },
	{ "8139too", realtek_dump_regs },
	{ "r8169", realtek_dump_regs },
	{ "de2104x", de2104x_dump_regs },
	{ "e1000", e1000_dump_regs },
	{ "e1000e", e1000_dump_regs },
	{ "igb", igb_dump_regs },
	{ "ixgb", ixgb_dump_regs },
	{ "ixgbe", ixgbe_dump_regs },
	{ "ixgbevf", ixgbevf_dump_regs },
	{ "natsemi", natsemi_dump_regs },
	{ "e100", e100_dump_regs },
	{ "amd8111e", amd8111e_dump_regs },
	{ "pcnet32", pcnet32_dump_regs },
	{ "fec_8xx", fec_8xx_dump_regs },
	{ "ibm_emac", ibm_emac_dump_regs },
	{ "tg3", tg3_dump_regs },
	{ "skge", skge_dump_regs },
	{ "sky2", sky2_dump_regs },
	{ "vioc", vioc_dump_regs },
	{ "smsc911x", smsc911x_dump_regs },
	{ "at76c50x-usb", at76c50x_usb_dump_regs },
	{ "sfc", sfc_dump_regs },
	{ "st_mac100", st_mac100_dump_regs },
	{ "st_gmac", st_gmac_dump_regs },
	{ "et131x", et131x_dump_regs },
	{ "altera_tse", altera_tse_dump_regs },
	{ "vmxnet3", vmxnet3_dump_regs },
	{ "fjes", fjes_dump_regs },
	{ "lan78xx", lan78xx_dump_regs },
	{ "dsa", dsa_dump_regs },
	{ "fec", fec_dump_regs },
	{ "igc", igc_dump_regs },
	{ "bnxt_en", bnxt_dump_regs },
	{ "cpsw-switch", cpsw_dump_regs },
	{ "lan743x", lan743x_dump_regs },
	{ "fsl_enetc", fsl_enetc_dump_regs },
	{ "fsl_enetc_vf", fsl_enetc_dump_regs },
	{ "hns3", hns3_dump_regs },
};
#endif

void dump_hex(FILE *file, const u8 *data, int len, int offset)
{
	int i;

	fprintf(file, "Offset\t\tValues\n");
	fprintf(file, "------\t\t------");
	for (i = 0; i < len; i++) {
		if (i % 16 == 0)
			fprintf(file, "\n0x%04x:\t\t", i + offset);
		fprintf(file, "%02x ", data[i]);
	}
	fprintf(file, "\n");
}

static int dump_regs(int gregs_dump_raw, int gregs_dump_hex,
		     struct ethtool_drvinfo *info, struct ethtool_regs *regs)
{
	if (gregs_dump_raw) {
		fwrite(regs->data, regs->len, 1, stdout);
		goto nested;
	}

#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
	if (!gregs_dump_hex) {
		unsigned int i;

		for (i = 0; i < ARRAY_SIZE(driver_list); i++)
			if (!strncmp(driver_list[i].name, info->driver,
				     ETHTOOL_BUSINFO_LEN)) {
				if (driver_list[i].func(info, regs) == 0)
					goto nested;
				/* This version (or some other
				 * variation in the dump format) is
				 * not handled; fall back to hex
				 */
				break;
			}
	}
#endif

	dump_hex(stdout, regs->data, regs->len, 0);

nested:
	/* Recurse dump if some drvinfo and regs structures are nested */
	if (info->regdump_len > regs->len + sizeof(*info) + sizeof(*regs)) {
		info = (struct ethtool_drvinfo *)(&regs->data[0] + regs->len);
		regs = (struct ethtool_regs *)(&regs->data[0] + regs->len + sizeof(*info));

		return dump_regs(gregs_dump_raw, gregs_dump_hex, info, regs);
	}

	return 0;
}

static int dump_eeprom(int geeprom_dump_raw,
		       struct ethtool_drvinfo *info __maybe_unused,
		       struct ethtool_eeprom *ee)
{
	if (geeprom_dump_raw) {
		fwrite(ee->data, 1, ee->len, stdout);
		return 0;
	}
#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
	if (!strncmp("natsemi", info->driver, ETHTOOL_BUSINFO_LEN)) {
		return natsemi_dump_eeprom(info, ee);
	} else if (!strncmp("tg3", info->driver, ETHTOOL_BUSINFO_LEN)) {
		return tg3_dump_eeprom(info, ee);
	}
#endif
	dump_hex(stdout, ee->data, ee->len, ee->offset);

	return 0;
}

static int dump_test(struct ethtool_test *test,
		     struct ethtool_gstrings *strings)
{
	unsigned int i;
	int rc;

	rc = test->flags & ETH_TEST_FL_FAILED;
	fprintf(stdout, "The test result is %s\n", rc ? "FAIL" : "PASS");

	if (test->flags & ETH_TEST_FL_EXTERNAL_LB)
		fprintf(stdout, "External loopback test was %sexecuted\n",
			(test->flags & ETH_TEST_FL_EXTERNAL_LB_DONE) ?
			"" : "not ");

	if (strings->len)
		fprintf(stdout, "The test extra info:\n");

	for (i = 0; i < strings->len; i++) {
		fprintf(stdout, "%s\t %d\n",
			(char *)(strings->data + i * ETH_GSTRING_LEN),
			(u32) test->data[i]);
	}

	fprintf(stdout, "\n");
	return rc;
}

static int dump_pause(const struct ethtool_pauseparam *epause,
		      u32 advertising, u32 lp_advertising)
{
	fprintf(stdout,
		"Autonegotiate:	%s\n"
		"RX:		%s\n"
		"TX:		%s\n",
		epause->autoneg ? "on" : "off",
		epause->rx_pause ? "on" : "off",
		epause->tx_pause ? "on" : "off");

	if (lp_advertising) {
		int an_rx = 0, an_tx = 0;

		/* Work out negotiated pause frame usage per
		 * IEEE 802.3-2005 table 28B-3.
		 */
		if (advertising & lp_advertising & ADVERTISED_Pause) {
			an_tx = 1;
			an_rx = 1;
		} else if (advertising & lp_advertising &
			   ADVERTISED_Asym_Pause) {
			if (advertising & ADVERTISED_Pause)
				an_rx = 1;
			else if (lp_advertising & ADVERTISED_Pause)
				an_tx = 1;
		}

		fprintf(stdout,
			"RX negotiated:	%s\n"
			"TX negotiated:	%s\n",
			an_rx ? "on" : "off",
			an_tx ? "on" : "off");
	}

	fprintf(stdout, "\n");
	return 0;
}

static int dump_ring(const struct ethtool_ringparam *ering)
{
	fprintf(stdout,
		"Pre-set maximums:\n"
		"RX:		%u\n"
		"RX Mini:	%u\n"
		"RX Jumbo:	%u\n"
		"TX:		%u\n",
		ering->rx_max_pending,
		ering->rx_mini_max_pending,
		ering->rx_jumbo_max_pending,
		ering->tx_max_pending);

	fprintf(stdout,
		"Current hardware settings:\n"
		"RX:		%u\n"
		"RX Mini:	%u\n"
		"RX Jumbo:	%u\n"
		"TX:		%u\n",
		ering->rx_pending,
		ering->rx_mini_pending,
		ering->rx_jumbo_pending,
		ering->tx_pending);

	fprintf(stdout, "\n");
	return 0;
}

static int dump_channels(const struct ethtool_channels *echannels)
{
	fprintf(stdout,
		"Pre-set maximums:\n"
		"RX:		%u\n"
		"TX:		%u\n"
		"Other:		%u\n"
		"Combined:	%u\n",
		echannels->max_rx, echannels->max_tx,
		echannels->max_other,
		echannels->max_combined);

	fprintf(stdout,
		"Current hardware settings:\n"
		"RX:		%u\n"
		"TX:		%u\n"
		"Other:		%u\n"
		"Combined:	%u\n",
		echannels->rx_count, echannels->tx_count,
		echannels->other_count,
		echannels->combined_count);

	fprintf(stdout, "\n");
	return 0;
}

static int dump_coalesce(const struct ethtool_coalesce *ecoal)
{
	fprintf(stdout, "Adaptive RX: %s  TX: %s\n",
		ecoal->use_adaptive_rx_coalesce ? "on" : "off",
		ecoal->use_adaptive_tx_coalesce ? "on" : "off");

	fprintf(stdout,
		"stats-block-usecs: %u\n"
		"sample-interval: %u\n"
		"pkt-rate-low: %u\n"
		"pkt-rate-high: %u\n"
		"\n"
		"rx-usecs: %u\n"
		"rx-frames: %u\n"
		"rx-usecs-irq: %u\n"
		"rx-frames-irq: %u\n"
		"\n"
		"tx-usecs: %u\n"
		"tx-frames: %u\n"
		"tx-usecs-irq: %u\n"
		"tx-frames-irq: %u\n"
		"\n"
		"rx-usecs-low: %u\n"
		"rx-frames-low: %u\n"
		"tx-usecs-low: %u\n"
		"tx-frames-low: %u\n"
		"\n"
		"rx-usecs-high: %u\n"
		"rx-frames-high: %u\n"
		"tx-usecs-high: %u\n"
		"tx-frames-high: %u\n"
		"\n",
		ecoal->stats_block_coalesce_usecs,
		ecoal->rate_sample_interval,
		ecoal->pkt_rate_low,
		ecoal->pkt_rate_high,

		ecoal->rx_coalesce_usecs,
		ecoal->rx_max_coalesced_frames,
		ecoal->rx_coalesce_usecs_irq,
		ecoal->rx_max_coalesced_frames_irq,

		ecoal->tx_coalesce_usecs,
		ecoal->tx_max_coalesced_frames,
		ecoal->tx_coalesce_usecs_irq,
		ecoal->tx_max_coalesced_frames_irq,

		ecoal->rx_coalesce_usecs_low,
		ecoal->rx_max_coalesced_frames_low,
		ecoal->tx_coalesce_usecs_low,
		ecoal->tx_max_coalesced_frames_low,

		ecoal->rx_coalesce_usecs_high,
		ecoal->rx_max_coalesced_frames_high,
		ecoal->tx_coalesce_usecs_high,
		ecoal->tx_max_coalesced_frames_high);

	return 0;
}

void dump_per_queue_coalesce(struct ethtool_per_queue_op *per_queue_opt,
			     __u32 *queue_mask, int n_queues)
{
	struct ethtool_coalesce *ecoal;
	int i, idx = 0;

	ecoal = (struct ethtool_coalesce *)(per_queue_opt + 1);
	for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); i++) {
		int queue = i * 32;
		__u32 mask = queue_mask[i];

		while (mask > 0) {
			if (mask & 0x1) {
				fprintf(stdout, "Queue: %d\n", queue);
				dump_coalesce(ecoal + idx);
				idx++;
			}
			mask = mask >> 1;
			queue++;
		}
		if (idx == n_queues)
			break;
	}
}

struct feature_state {
	u32 off_flags;
	struct ethtool_gfeatures features;
};

static void dump_one_feature(const char *indent, const char *name,
			     const struct feature_state *state,
			     const struct feature_state *ref_state,
			     u32 index)
{
	if (ref_state &&
	    !(FEATURE_BIT_IS_SET(state->features.features, index, active) ^
	      FEATURE_BIT_IS_SET(ref_state->features.features, index, active)))
		return;

	printf("%s%s: %s%s\n",
	       indent, name,
	       FEATURE_BIT_IS_SET(state->features.features, index, active) ?
	       "on" : "off",
	       (!FEATURE_BIT_IS_SET(state->features.features, index, available)
		|| FEATURE_BIT_IS_SET(state->features.features, index,
				      never_changed))
	       ? " [fixed]"
	       : (FEATURE_BIT_IS_SET(state->features.features, index, requested)
		  ^ FEATURE_BIT_IS_SET(state->features.features, index, active))
	       ? (FEATURE_BIT_IS_SET(state->features.features, index, requested)
		  ? " [requested on]" : " [requested off]")
	       : "");
}

static unsigned int linux_version_code(void)
{
	struct utsname utsname;
	unsigned version, patchlevel, sublevel = 0;

	if (uname(&utsname))
		return -1;
	if (sscanf(utsname.release, "%u.%u.%u", &version, &patchlevel, &sublevel) < 2)
		return -1;
	return KERNEL_VERSION(version, patchlevel, sublevel);
}

static void dump_features(const struct feature_defs *defs,
			  const struct feature_state *state,
			  const struct feature_state *ref_state)
{
	unsigned int kernel_ver = linux_version_code();
	unsigned int i, j;
	int indent;
	u32 value;

	for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
		/* Don't show features whose state is unknown on this
		 * kernel version
		 */
		if (defs->off_flag_matched[i] == 0 &&
		    ((off_flag_def[i].get_cmd == 0 &&
		      kernel_ver < off_flag_def[i].min_kernel_ver) ||
		     (off_flag_def[i].get_cmd == ETHTOOL_GUFO &&
		      kernel_ver >= KERNEL_VERSION(4, 14, 0))))
			continue;

		value = off_flag_def[i].value;

		/* If this offload flag matches exactly one generic
		 * feature then it's redundant to show the flag and
		 * feature states separately.  Otherwise, show the
		 * flag state first.
		 */
		if (defs->off_flag_matched[i] != 1 &&
		    (!ref_state ||
		     (state->off_flags ^ ref_state->off_flags) & value)) {
			printf("%s: %s\n",
			       off_flag_def[i].long_name,
			       (state->off_flags & value) ? "on" : "off");
			indent = 1;
		} else {
			indent = 0;
		}

		/* Show matching features */
		for (j = 0; j < defs->n_features; j++) {
			if (defs->def[j].off_flag_index != (int)i)
				continue;
			if (defs->off_flag_matched[i] != 1)
				/* Show all matching feature states */
				dump_one_feature(indent ? "\t" : "",
						 defs->def[j].name,
						 state, ref_state, j);
			else
				/* Show full state with the old flag name */
				dump_one_feature("", off_flag_def[i].long_name,
						 state, ref_state, j);
		}
	}

	/* Show all unmatched features that have non-null names */
	for (j = 0; j < defs->n_features; j++)
		if (defs->def[j].off_flag_index < 0 && defs->def[j].name[0])
			dump_one_feature("", defs->def[j].name,
					 state, ref_state, j);
}

static int dump_rxfhash(int fhash, u64 val)
{
	switch (fhash & ~FLOW_RSS) {
	case TCP_V4_FLOW:
		fprintf(stdout, "TCP over IPV4 flows");
		break;
	case UDP_V4_FLOW:
		fprintf(stdout, "UDP over IPV4 flows");
		break;
	case SCTP_V4_FLOW:
		fprintf(stdout, "SCTP over IPV4 flows");
		break;
	case AH_ESP_V4_FLOW:
	case AH_V4_FLOW:
	case ESP_V4_FLOW:
		fprintf(stdout, "IPSEC AH/ESP over IPV4 flows");
		break;
	case TCP_V6_FLOW:
		fprintf(stdout, "TCP over IPV6 flows");
		break;
	case UDP_V6_FLOW:
		fprintf(stdout, "UDP over IPV6 flows");
		break;
	case SCTP_V6_FLOW:
		fprintf(stdout, "SCTP over IPV6 flows");
		break;
	case AH_ESP_V6_FLOW:
	case AH_V6_FLOW:
	case ESP_V6_FLOW:
		fprintf(stdout, "IPSEC AH/ESP over IPV6 flows");
		break;
	default:
		break;
	}

	if (val & RXH_DISCARD) {
		fprintf(stdout, " - All matching flows discarded on RX\n");
		return 0;
	}
	fprintf(stdout, " use these fields for computing Hash flow key:\n");

	fprintf(stdout, "%s\n", unparse_rxfhashopts(val));

	return 0;
}

static void dump_eeecmd(struct ethtool_eee *ep)
{
	ETHTOOL_DECLARE_LINK_MODE_MASK(link_mode);

	fprintf(stdout, "	EEE status: ");
	if (!ep->supported) {
		fprintf(stdout, "not supported\n");
		return;
	} else if (!ep->eee_enabled) {
		fprintf(stdout, "disabled\n");
	} else {
		fprintf(stdout, "enabled - ");
		if (ep->eee_active)
			fprintf(stdout, "active\n");
		else
			fprintf(stdout, "inactive\n");
	}

	fprintf(stdout, "	Tx LPI:");
	if (ep->tx_lpi_enabled)
		fprintf(stdout, " %d (us)\n", ep->tx_lpi_timer);
	else
		fprintf(stdout, " disabled\n");

	ethtool_link_mode_zero(link_mode);

	link_mode[0] = ep->supported;
	dump_link_caps("Supported EEE", "", link_mode, 1);

	link_mode[0] = ep->advertised;
	dump_link_caps("Advertised EEE", "", link_mode, 1);

	link_mode[0] = ep->lp_advertised;
	dump_link_caps("Link partner advertised EEE", "", link_mode, 1);
}

static void dump_fec(u32 fec)
{
	if (fec & ETHTOOL_FEC_NONE)
		fprintf(stdout, " None");
	if (fec & ETHTOOL_FEC_AUTO)
		fprintf(stdout, " Auto");
	if (fec & ETHTOOL_FEC_OFF)
		fprintf(stdout, " Off");
	if (fec & ETHTOOL_FEC_BASER)
		fprintf(stdout, " BaseR");
	if (fec & ETHTOOL_FEC_RS)
		fprintf(stdout, " RS");
	if (fec & ETHTOOL_FEC_LLRS)
		fprintf(stdout, " LLRS");
}

#define N_SOTS 7

static char *so_timestamping_labels[N_SOTS] = {
	"hardware-transmit     (SOF_TIMESTAMPING_TX_HARDWARE)",
	"software-transmit     (SOF_TIMESTAMPING_TX_SOFTWARE)",
	"hardware-receive      (SOF_TIMESTAMPING_RX_HARDWARE)",
	"software-receive      (SOF_TIMESTAMPING_RX_SOFTWARE)",
	"software-system-clock (SOF_TIMESTAMPING_SOFTWARE)",
	"hardware-legacy-clock (SOF_TIMESTAMPING_SYS_HARDWARE)",
	"hardware-raw-clock    (SOF_TIMESTAMPING_RAW_HARDWARE)",
};

#define N_TX_TYPES (HWTSTAMP_TX_ONESTEP_SYNC + 1)

static char *tx_type_labels[N_TX_TYPES] = {
	"off                   (HWTSTAMP_TX_OFF)",
	"on                    (HWTSTAMP_TX_ON)",
	"one-step-sync         (HWTSTAMP_TX_ONESTEP_SYNC)",
};

#define N_RX_FILTERS (HWTSTAMP_FILTER_NTP_ALL + 1)

static char *rx_filter_labels[N_RX_FILTERS] = {
	"none                  (HWTSTAMP_FILTER_NONE)",
	"all                   (HWTSTAMP_FILTER_ALL)",
	"some                  (HWTSTAMP_FILTER_SOME)",
	"ptpv1-l4-event        (HWTSTAMP_FILTER_PTP_V1_L4_EVENT)",
	"ptpv1-l4-sync         (HWTSTAMP_FILTER_PTP_V1_L4_SYNC)",
	"ptpv1-l4-delay-req    (HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ)",
	"ptpv2-l4-event        (HWTSTAMP_FILTER_PTP_V2_L4_EVENT)",
	"ptpv2-l4-sync         (HWTSTAMP_FILTER_PTP_V2_L4_SYNC)",
	"ptpv2-l4-delay-req    (HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ)",
	"ptpv2-l2-event        (HWTSTAMP_FILTER_PTP_V2_L2_EVENT)",
	"ptpv2-l2-sync         (HWTSTAMP_FILTER_PTP_V2_L2_SYNC)",
	"ptpv2-l2-delay-req    (HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ)",
	"ptpv2-event           (HWTSTAMP_FILTER_PTP_V2_EVENT)",
	"ptpv2-sync            (HWTSTAMP_FILTER_PTP_V2_SYNC)",
	"ptpv2-delay-req       (HWTSTAMP_FILTER_PTP_V2_DELAY_REQ)",
	"ntp-all               (HWTSTAMP_FILTER_NTP_ALL)",
};

static int dump_tsinfo(const struct ethtool_ts_info *info)
{
	int i;

	fprintf(stdout, "Capabilities:\n");

	for (i = 0; i < N_SOTS; i++) {
		if (info->so_timestamping & (1 << i))
			fprintf(stdout, "\t%s\n", so_timestamping_labels[i]);
	}

	fprintf(stdout, "PTP Hardware Clock: ");

	if (info->phc_index < 0)
		fprintf(stdout, "none\n");
	else
		fprintf(stdout, "%d\n", info->phc_index);

	fprintf(stdout, "Hardware Transmit Timestamp Modes:");

	if (!info->tx_types)
		fprintf(stdout, " none\n");
	else
		fprintf(stdout, "\n");

	for (i = 0; i < N_TX_TYPES; i++) {
		if (info->tx_types & (1 << i))
			fprintf(stdout,	"\t%s\n", tx_type_labels[i]);
	}

	fprintf(stdout, "Hardware Receive Filter Modes:");

	if (!info->rx_filters)
		fprintf(stdout, " none\n");
	else
		fprintf(stdout, "\n");

	for (i = 0; i < N_RX_FILTERS; i++) {
		if (info->rx_filters & (1 << i))
			fprintf(stdout, "\t%s\n", rx_filter_labels[i]);
	}

	return 0;
}

static struct ethtool_gstrings *
get_stringset(struct cmd_context *ctx, enum ethtool_stringset set_id,
	      ptrdiff_t drvinfo_offset, int null_terminate)
{
	struct {
		struct ethtool_sset_info hdr;
		u32 buf[1];
	} sset_info;
	struct ethtool_drvinfo drvinfo;
	u32 len, i;
	struct ethtool_gstrings *strings;

	sset_info.hdr.cmd = ETHTOOL_GSSET_INFO;
	sset_info.hdr.reserved = 0;
	sset_info.hdr.sset_mask = 1ULL << set_id;
	if (send_ioctl(ctx, &sset_info) == 0) {
		const u32 *sset_lengths = sset_info.hdr.data;

		len = sset_info.hdr.sset_mask ? sset_lengths[0] : 0;
	} else if (errno == EOPNOTSUPP && drvinfo_offset != 0) {
		/* Fallback for old kernel versions */
		drvinfo.cmd = ETHTOOL_GDRVINFO;
		if (send_ioctl(ctx, &drvinfo))
			return NULL;
		len = *(u32 *)((char *)&drvinfo + drvinfo_offset);
	} else {
		return NULL;
	}

	strings = calloc(1, sizeof(*strings) + len * ETH_GSTRING_LEN);
	if (!strings)
		return NULL;

	strings->cmd = ETHTOOL_GSTRINGS;
	strings->string_set = set_id;
	strings->len = len;
	if (len != 0 && send_ioctl(ctx, strings)) {
		free(strings);
		return NULL;
	}

	if (null_terminate)
		for (i = 0; i < len; i++)
			strings->data[(i + 1) * ETH_GSTRING_LEN - 1] = 0;

	return strings;
}

static struct feature_defs *get_feature_defs(struct cmd_context *ctx)
{
	struct ethtool_gstrings *names;
	struct feature_defs *defs;
	unsigned int i, j;
	u32 n_features;

	names = get_stringset(ctx, ETH_SS_FEATURES, 0, 1);
	if (names) {
		n_features = names->len;
	} else if (errno == EOPNOTSUPP || errno == EINVAL) {
		/* Kernel doesn't support named features; not an error */
		n_features = 0;
	} else if (errno == EPERM) {
		/* Kernel bug: ETHTOOL_GSSET_INFO was privileged.
		 * Work around it. */
		n_features = 0;
	} else {
		return NULL;
	}

	defs = malloc(sizeof(*defs) + sizeof(defs->def[0]) * n_features);
	if (!defs) {
		free(names);
		return NULL;
	}

	defs->n_features = n_features;
	memset(defs->off_flag_matched, 0, sizeof(defs->off_flag_matched));

	/* Copy out feature names and find those associated with legacy flags */
	for (i = 0; i < defs->n_features; i++) {
		memcpy(defs->def[i].name, names->data + i * ETH_GSTRING_LEN,
		       ETH_GSTRING_LEN);
		defs->def[i].off_flag_index = -1;

		for (j = 0;
		     j < OFF_FLAG_DEF_SIZE &&
			     defs->def[i].off_flag_index < 0;
		     j++) {
			const char *pattern =
				off_flag_def[j].kernel_name;
			const char *name = defs->def[i].name;
			for (;;) {
				if (*pattern == '*') {
					/* There is only one wildcard; so
					 * switch to a suffix comparison */
					size_t pattern_len =
						strlen(pattern + 1);
					size_t name_len = strlen(name);
					if (name_len < pattern_len)
						break; /* name is too short */
					name += name_len - pattern_len;
					++pattern;
				} else if (*pattern != *name) {
					break; /* mismatch */
				} else if (*pattern == 0) {
					defs->def[i].off_flag_index = j;
					defs->off_flag_matched[j]++;
					break;
				} else {
					++name;
					++pattern;
				}
			}
		}
	}

	free(names);
	return defs;
}

static int do_gdrv(struct cmd_context *ctx)
{
	int err;
	struct ethtool_drvinfo drvinfo;

	if (ctx->argc != 0)
		exit_bad_args();

	drvinfo.cmd = ETHTOOL_GDRVINFO;
	err = send_ioctl(ctx, &drvinfo);
	if (err < 0) {
		perror("Cannot get driver information");
		return 71;
	}
	return dump_drvinfo(&drvinfo);
}

static int do_gpause(struct cmd_context *ctx)
{
	struct ethtool_pauseparam epause;
	struct ethtool_cmd ecmd;
	int err;

	if (ctx->argc != 0)
		exit_bad_args();

	fprintf(stdout, "Pause parameters for %s:\n", ctx->devname);

	epause.cmd = ETHTOOL_GPAUSEPARAM;
	err = send_ioctl(ctx, &epause);
	if (err) {
		perror("Cannot get device pause settings");
		return 76;
	}

	if (epause.autoneg) {
		ecmd.cmd = ETHTOOL_GSET;
		err = send_ioctl(ctx, &ecmd);
		if (err) {
			perror("Cannot get device settings");
			return 1;
		}
		dump_pause(&epause, ecmd.advertising, ecmd.lp_advertising);
	} else {
		dump_pause(&epause, 0, 0);
	}

	return 0;
}

static void do_generic_set1(struct cmdline_info *info, int *changed_out)
{
	int wanted, *v1, *v2;

	v1 = info->wanted_val;
	wanted = *v1;

	if (wanted < 0)
		return;

	v2 = info->ioctl_val;
	if (wanted == *v2) {
		fprintf(stderr, "%s unmodified, ignoring\n", info->name);
	} else {
		*v2 = wanted;
		*changed_out = 1;
	}
}

static void do_generic_set(struct cmdline_info *info,
			   unsigned int n_info,
			   int *changed_out)
{
	unsigned int i;

	for (i = 0; i < n_info; i++)
		do_generic_set1(&info[i], changed_out);
}

static int do_spause(struct cmd_context *ctx)
{
	struct ethtool_pauseparam epause;
	int gpause_changed = 0;
	int pause_autoneg_wanted = -1;
	int pause_rx_wanted = -1;
	int pause_tx_wanted = -1;
	struct cmdline_info cmdline_pause[] = {
		{
			.name		= "autoneg",
			.type		= CMDL_BOOL,
			.wanted_val	= &pause_autoneg_wanted,
			.ioctl_val	= &epause.autoneg,
		},
		{
			.name		= "rx",
			.type		= CMDL_BOOL,
			.wanted_val	= &pause_rx_wanted,
			.ioctl_val	= &epause.rx_pause,
		},
		{
			.name		= "tx",
			.type		= CMDL_BOOL,
			.wanted_val	= &pause_tx_wanted,
			.ioctl_val	= &epause.tx_pause,
		},
	};
	int err, changed = 0;

	parse_generic_cmdline(ctx, &gpause_changed,
			      cmdline_pause, ARRAY_SIZE(cmdline_pause));

	epause.cmd = ETHTOOL_GPAUSEPARAM;
	err = send_ioctl(ctx, &epause);
	if (err) {
		perror("Cannot get device pause settings");
		return 77;
	}

	do_generic_set(cmdline_pause, ARRAY_SIZE(cmdline_pause), &changed);

	if (!changed) {
		fprintf(stderr, "no pause parameters changed, aborting\n");
		return 78;
	}

	epause.cmd = ETHTOOL_SPAUSEPARAM;
	err = send_ioctl(ctx, &epause);
	if (err) {
		perror("Cannot set device pause parameters");
		return 79;
	}

	return 0;
}

static int do_sring(struct cmd_context *ctx)
{
	struct ethtool_ringparam ering;
	int gring_changed = 0;
	s32 ring_rx_wanted = -1;
	s32 ring_rx_mini_wanted = -1;
	s32 ring_rx_jumbo_wanted = -1;
	s32 ring_tx_wanted = -1;
	struct cmdline_info cmdline_ring[] = {
		{
			.name		= "rx",
			.type		= CMDL_S32,
			.wanted_val	= &ring_rx_wanted,
			.ioctl_val	= &ering.rx_pending,
		},
		{
			.name		= "rx-mini",
			.type		= CMDL_S32,
			.wanted_val	= &ring_rx_mini_wanted,
			.ioctl_val	= &ering.rx_mini_pending,
		},
		{
			.name		= "rx-jumbo",
			.type		= CMDL_S32,
			.wanted_val	= &ring_rx_jumbo_wanted,
			.ioctl_val	= &ering.rx_jumbo_pending,
		},
		{
			.name		= "tx",
			.type		= CMDL_S32,
			.wanted_val	= &ring_tx_wanted,
			.ioctl_val	= &ering.tx_pending,
		},
	};
	int err, changed = 0;

	parse_generic_cmdline(ctx, &gring_changed,
			      cmdline_ring, ARRAY_SIZE(cmdline_ring));

	ering.cmd = ETHTOOL_GRINGPARAM;
	err = send_ioctl(ctx, &ering);
	if (err) {
		perror("Cannot get device ring settings");
		return 76;
	}

	do_generic_set(cmdline_ring, ARRAY_SIZE(cmdline_ring), &changed);

	if (!changed) {
		fprintf(stderr, "no ring parameters changed, aborting\n");
		return 80;
	}

	ering.cmd = ETHTOOL_SRINGPARAM;
	err = send_ioctl(ctx, &ering);
	if (err) {
		perror("Cannot set device ring parameters");
		return 81;
	}

	return 0;
}

static int do_gring(struct cmd_context *ctx)
{
	struct ethtool_ringparam ering;
	int err;

	if (ctx->argc != 0)
		exit_bad_args();

	fprintf(stdout, "Ring parameters for %s:\n", ctx->devname);

	ering.cmd = ETHTOOL_GRINGPARAM;
	err = send_ioctl(ctx, &ering);
	if (err == 0) {
		err = dump_ring(&ering);
		if (err)
			return err;
	} else {
		perror("Cannot get device ring settings");
		return 76;
	}

	return 0;
}

static int do_schannels(struct cmd_context *ctx)
{
	struct ethtool_channels echannels;
	int gchannels_changed;
	s32 channels_rx_wanted = -1;
	s32 channels_tx_wanted = -1;
	s32 channels_other_wanted = -1;
	s32 channels_combined_wanted = -1;
	struct cmdline_info cmdline_channels[] = {
		{
			.name		= "rx",
			.type		= CMDL_S32,
			.wanted_val	= &channels_rx_wanted,
			.ioctl_val	= &echannels.rx_count,
		},
		{
			.name		= "tx",
			.type		= CMDL_S32,
			.wanted_val	= &channels_tx_wanted,
			.ioctl_val	= &echannels.tx_count,
		},
		{
			.name		= "other",
			.type		= CMDL_S32,
			.wanted_val	= &channels_other_wanted,
			.ioctl_val	= &echannels.other_count,
		},
		{
			.name		= "combined",
			.type		= CMDL_S32,
			.wanted_val	= &channels_combined_wanted,
			.ioctl_val	= &echannels.combined_count,
		},
	};
	int err, changed = 0;

	parse_generic_cmdline(ctx, &gchannels_changed,
			      cmdline_channels, ARRAY_SIZE(cmdline_channels));

	echannels.cmd = ETHTOOL_GCHANNELS;
	err = send_ioctl(ctx, &echannels);
	if (err) {
		perror("Cannot get device channel parameters");
		return 1;
	}

	do_generic_set(cmdline_channels, ARRAY_SIZE(cmdline_channels),
			&changed);

	if (!changed) {
		fprintf(stderr, "no channel parameters changed.\n");
		fprintf(stderr, "current values: rx %u tx %u other %u"
			" combined %u\n", echannels.rx_count,
			echannels.tx_count, echannels.other_count,
			echannels.combined_count);
		return 0;
	}

	echannels.cmd = ETHTOOL_SCHANNELS;
	err = send_ioctl(ctx, &echannels);
	if (err) {
		perror("Cannot set device channel parameters");
		return 1;
	}

	return 0;
}

static int do_gchannels(struct cmd_context *ctx)
{
	struct ethtool_channels echannels;
	int err;

	if (ctx->argc != 0)
		exit_bad_args();

	fprintf(stdout, "Channel parameters for %s:\n", ctx->devname);

	echannels.cmd = ETHTOOL_GCHANNELS;
	err = send_ioctl(ctx, &echannels);
	if (err == 0) {
		err = dump_channels(&echannels);
		if (err)
			return err;
	} else {
		perror("Cannot get device channel parameters");
		return 1;
	}
	return 0;

}

static int do_gcoalesce(struct cmd_context *ctx)
{
	struct ethtool_coalesce ecoal = {};
	int err;

	if (ctx->argc != 0)
		exit_bad_args();

	fprintf(stdout, "Coalesce parameters for %s:\n", ctx->devname);

	ecoal.cmd = ETHTOOL_GCOALESCE;
	err = send_ioctl(ctx, &ecoal);
	if (err == 0) {
		err = dump_coalesce(&ecoal);
		if (err)
			return err;
	} else {
		perror("Cannot get device coalesce settings");
		return 82;
	}

	return 0;
}

#define DECLARE_COALESCE_OPTION_VARS()		\
	s32 coal_stats_wanted = -1;		\
	int coal_adaptive_rx_wanted = -1;	\
	int coal_adaptive_tx_wanted = -1;	\
	s32 coal_sample_rate_wanted = -1;	\
	s32 coal_pkt_rate_low_wanted = -1;	\
	s32 coal_pkt_rate_high_wanted = -1;	\
	s32 coal_rx_usec_wanted = -1;		\
	s32 coal_rx_frames_wanted = -1;		\
	s32 coal_rx_usec_irq_wanted = -1;	\
	s32 coal_rx_frames_irq_wanted = -1;	\
	s32 coal_tx_usec_wanted = -1;		\
	s32 coal_tx_frames_wanted = -1;		\
	s32 coal_tx_usec_irq_wanted = -1;	\
	s32 coal_tx_frames_irq_wanted = -1;	\
	s32 coal_rx_usec_low_wanted = -1;	\
	s32 coal_rx_frames_low_wanted = -1;	\
	s32 coal_tx_usec_low_wanted = -1;	\
	s32 coal_tx_frames_low_wanted = -1;	\
	s32 coal_rx_usec_high_wanted = -1;	\
	s32 coal_rx_frames_high_wanted = -1;	\
	s32 coal_tx_usec_high_wanted = -1;	\
	s32 coal_tx_frames_high_wanted = -1

#define COALESCE_CMDLINE_INFO(__ecoal)					\
{									\
	{								\
		.name		= "adaptive-rx",			\
		.type		= CMDL_BOOL,				\
		.wanted_val	= &coal_adaptive_rx_wanted,		\
		.ioctl_val	= &__ecoal.use_adaptive_rx_coalesce,	\
	},								\
	{								\
		.name		= "adaptive-tx",			\
		.type		= CMDL_BOOL,				\
		.wanted_val	= &coal_adaptive_tx_wanted,		\
		.ioctl_val	= &__ecoal.use_adaptive_tx_coalesce,	\
	},								\
	{								\
		.name		= "sample-interval",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_sample_rate_wanted,		\
		.ioctl_val	= &__ecoal.rate_sample_interval,	\
	},								\
	{								\
		.name		= "stats-block-usecs",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_stats_wanted,			\
		.ioctl_val	= &__ecoal.stats_block_coalesce_usecs,	\
	},								\
	{								\
		.name		= "pkt-rate-low",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_pkt_rate_low_wanted,		\
		.ioctl_val	= &__ecoal.pkt_rate_low,		\
	},								\
	{								\
		.name		= "pkt-rate-high",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_pkt_rate_high_wanted,		\
		.ioctl_val	= &__ecoal.pkt_rate_high,		\
	},								\
	{								\
		.name		= "rx-usecs",				\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_rx_usec_wanted,			\
		.ioctl_val	= &__ecoal.rx_coalesce_usecs,		\
	},								\
	{								\
		.name		= "rx-frames",				\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_rx_frames_wanted,		\
		.ioctl_val	= &__ecoal.rx_max_coalesced_frames,	\
	},								\
	{								\
		.name		= "rx-usecs-irq",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_rx_usec_irq_wanted,		\
		.ioctl_val	= &__ecoal.rx_coalesce_usecs_irq,	\
	},								\
	{								\
		.name		= "rx-frames-irq",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_rx_frames_irq_wanted,		\
		.ioctl_val	= &__ecoal.rx_max_coalesced_frames_irq,	\
	},								\
	{								\
		.name		= "tx-usecs",				\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_tx_usec_wanted,			\
		.ioctl_val	= &__ecoal.tx_coalesce_usecs,		\
	},								\
	{								\
		.name		= "tx-frames",				\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_tx_frames_wanted,		\
		.ioctl_val	= &__ecoal.tx_max_coalesced_frames,	\
	},								\
	{								\
		.name		= "tx-usecs-irq",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_tx_usec_irq_wanted,		\
		.ioctl_val	= &__ecoal.tx_coalesce_usecs_irq,	\
	},								\
	{								\
		.name		= "tx-frames-irq",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_tx_frames_irq_wanted,		\
		.ioctl_val	= &__ecoal.tx_max_coalesced_frames_irq,	\
	},								\
	{								\
		.name		= "rx-usecs-low",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_rx_usec_low_wanted,		\
		.ioctl_val	= &__ecoal.rx_coalesce_usecs_low,	\
	},								\
	{								\
		.name		= "rx-frames-low",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_rx_frames_low_wanted,		\
		.ioctl_val	= &__ecoal.rx_max_coalesced_frames_low,	\
	},								\
	{								\
		.name		= "tx-usecs-low",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_tx_usec_low_wanted,		\
		.ioctl_val	= &__ecoal.tx_coalesce_usecs_low,	\
	},								\
	{								\
		.name		= "tx-frames-low",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_tx_frames_low_wanted,		\
		.ioctl_val	= &__ecoal.tx_max_coalesced_frames_low,	\
	},								\
	{								\
		.name		= "rx-usecs-high",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_rx_usec_high_wanted,		\
		.ioctl_val	= &__ecoal.rx_coalesce_usecs_high,	\
	},								\
	{								\
		.name		= "rx-frames-high",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_rx_frames_high_wanted,		\
		.ioctl_val	= &__ecoal.rx_max_coalesced_frames_high,\
	},								\
	{								\
		.name		= "tx-usecs-high",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_tx_usec_high_wanted,		\
		.ioctl_val	= &__ecoal.tx_coalesce_usecs_high,	\
	},								\
	{								\
		.name		= "tx-frames-high",			\
		.type		= CMDL_S32,				\
		.wanted_val	= &coal_tx_frames_high_wanted,		\
		.ioctl_val	= &__ecoal.tx_max_coalesced_frames_high,\
	},								\
}

static int do_scoalesce(struct cmd_context *ctx)
{
	struct ethtool_coalesce ecoal;
	int gcoalesce_changed = 0;
	DECLARE_COALESCE_OPTION_VARS();
	struct cmdline_info cmdline_coalesce[] = COALESCE_CMDLINE_INFO(ecoal);
	int err, changed = 0;

	parse_generic_cmdline(ctx, &gcoalesce_changed,
			      cmdline_coalesce, ARRAY_SIZE(cmdline_coalesce));

	ecoal.cmd = ETHTOOL_GCOALESCE;
	err = send_ioctl(ctx, &ecoal);
	if (err) {
		perror("Cannot get device coalesce settings");
		return 76;
	}

	do_generic_set(cmdline_coalesce, ARRAY_SIZE(cmdline_coalesce),
		       &changed);

	if (!changed) {
		fprintf(stderr, "no coalesce parameters changed, aborting\n");
		return 80;
	}

	ecoal.cmd = ETHTOOL_SCOALESCE;
	err = send_ioctl(ctx, &ecoal);
	if (err) {
		perror("Cannot set device coalesce parameters");
		return 81;
	}

	return 0;
}

static struct feature_state *
get_features(struct cmd_context *ctx, const struct feature_defs *defs)
{
	struct feature_state *state;
	struct ethtool_value eval;
	int err, allfail = 1;
	u32 value;
	int i;

	state = malloc(sizeof(*state) +
		       FEATURE_BITS_TO_BLOCKS(defs->n_features) *
		       sizeof(state->features.features[0]));
	if (!state)
		return NULL;

	state->off_flags = 0;

	for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
		value = off_flag_def[i].value;
		if (!off_flag_def[i].get_cmd)
			continue;
		eval.cmd = off_flag_def[i].get_cmd;
		err = send_ioctl(ctx, &eval);
		if (err) {
			if (errno == EOPNOTSUPP &&
			    off_flag_def[i].get_cmd == ETHTOOL_GUFO)
				continue;

			fprintf(stderr,
				"Cannot get device %s settings: %m\n",
				off_flag_def[i].long_name);
		} else {
			if (eval.data)
				state->off_flags |= value;
			allfail = 0;
		}
	}

	eval.cmd = ETHTOOL_GFLAGS;
	err = send_ioctl(ctx, &eval);
	if (err) {
		perror("Cannot get device flags");
	} else {
		state->off_flags |= eval.data & ETH_FLAG_EXT_MASK;
		allfail = 0;
	}

	if (defs->n_features) {
		state->features.cmd = ETHTOOL_GFEATURES;
		state->features.size = FEATURE_BITS_TO_BLOCKS(defs->n_features);
		err = send_ioctl(ctx, &state->features);
		if (err)
			perror("Cannot get device generic features");
		else
			allfail = 0;
	}

	if (allfail) {
		free(state);
		return NULL;
	}

	return state;
}

static int do_gfeatures(struct cmd_context *ctx)
{
	struct feature_defs *defs;
	struct feature_state *features;

	if (ctx->argc != 0)
		exit_bad_args();

	defs = get_feature_defs(ctx);
	if (!defs) {
		perror("Cannot get device feature names");
		return 1;
	}

	fprintf(stdout, "Features for %s:\n", ctx->devname);

	features = get_features(ctx, defs);
	if (!features) {
		fprintf(stdout, "no feature info available\n");
		free(defs);
		return 1;
	}

	dump_features(defs, features, NULL);
	free(features);
	free(defs);
	return 0;
}

static int do_sfeatures(struct cmd_context *ctx)
{
	struct feature_defs *defs;
	int any_changed = 0, any_mismatch = 0;
	u32 off_flags_wanted = 0;
	u32 off_flags_mask = 0;
	struct ethtool_sfeatures *efeatures = NULL;
	struct feature_state *old_state = NULL;
	struct feature_state *new_state = NULL;
	struct cmdline_info *cmdline_features;
	struct ethtool_value eval;
	unsigned int i, j;
	int err, rc;

	defs = get_feature_defs(ctx);
	if (!defs) {
		perror("Cannot get device feature names");
		return 1;
	}
	if (defs->n_features) {
		efeatures = malloc(sizeof(*efeatures) +
				   FEATURE_BITS_TO_BLOCKS(defs->n_features) *
				   sizeof(efeatures->features[0]));
		if (!efeatures) {
			perror("Cannot parse arguments");
			rc = 1;
			goto err;
		}
		efeatures->cmd = ETHTOOL_SFEATURES;
		efeatures->size = FEATURE_BITS_TO_BLOCKS(defs->n_features);
		memset(efeatures->features, 0,
		       FEATURE_BITS_TO_BLOCKS(defs->n_features) *
		       sizeof(efeatures->features[0]));
	}

	/* Generate cmdline_info for legacy flags and kernel-named
	 * features, and parse our arguments.
	 */
	cmdline_features = calloc(2 * OFF_FLAG_DEF_SIZE + defs->n_features,
				  sizeof(cmdline_features[0]));
	if (!cmdline_features) {
		perror("Cannot parse arguments");
		rc = 1;
		goto err;
	}
	j = 0;
	for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
		flag_to_cmdline_info(off_flag_def[i].short_name,
				     off_flag_def[i].value,
				     &off_flags_wanted, &off_flags_mask,
				     &cmdline_features[j++]);
		flag_to_cmdline_info(off_flag_def[i].long_name,
				     off_flag_def[i].value,
				     &off_flags_wanted, &off_flags_mask,
				     &cmdline_features[j++]);
	}
	for (i = 0; i < defs->n_features; i++)
		flag_to_cmdline_info(
			defs->def[i].name, FEATURE_FIELD_FLAG(i),
			&FEATURE_WORD(efeatures->features, i, requested),
			&FEATURE_WORD(efeatures->features, i, valid),
			&cmdline_features[j++]);
	parse_generic_cmdline(ctx, &any_changed, cmdline_features,
			      2 * OFF_FLAG_DEF_SIZE + defs->n_features);
	free(cmdline_features);

	if (!any_changed) {
		fprintf(stdout, "no features changed\n");
		rc = 0;
		goto err;
	}

	old_state = get_features(ctx, defs);
	if (!old_state) {
		rc = 1;
		goto err;
	}

	if (efeatures) {
		/* For each offload that the user specified, update any
		 * related features that the user did not specify and that
		 * are not fixed.  Warn if all related features are fixed.
		 */
		for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
			int fixed = 1;

			if (!(off_flags_mask & off_flag_def[i].value))
				continue;

			for (j = 0; j < defs->n_features; j++) {
				if (defs->def[j].off_flag_index != (int)i ||
				    !FEATURE_BIT_IS_SET(
					    old_state->features.features,
					    j, available) ||
				    FEATURE_BIT_IS_SET(
					    old_state->features.features,
					    j, never_changed))
					continue;

				fixed = 0;
				if (!FEATURE_BIT_IS_SET(efeatures->features,
							j, valid)) {
					FEATURE_BIT_SET(efeatures->features,
							j, valid);
					if (off_flags_wanted &
					    off_flag_def[i].value)
						FEATURE_BIT_SET(
							efeatures->features,
							j, requested);
				}
			}

			if (fixed)
				fprintf(stderr, "Cannot change %s\n",
					off_flag_def[i].long_name);
		}

		err = send_ioctl(ctx, efeatures);
		if (err < 0) {
			perror("Cannot set device feature settings");
			rc = 1;
			goto err;
		}
	} else {
		for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
			if (!off_flag_def[i].set_cmd)
				continue;
			if (off_flags_mask & off_flag_def[i].value) {
				eval.cmd = off_flag_def[i].set_cmd;
				eval.data = !!(off_flags_wanted &
					       off_flag_def[i].value);
				err = send_ioctl(ctx, &eval);
				if (err) {
					fprintf(stderr,
						"Cannot set device %s settings: %m\n",
						off_flag_def[i].long_name);
					rc = 1;
					goto err;
				}
			}
		}

		if (off_flags_mask & ETH_FLAG_EXT_MASK) {
			eval.cmd = ETHTOOL_SFLAGS;
			eval.data = (old_state->off_flags & ~off_flags_mask &
				     ETH_FLAG_EXT_MASK);
			eval.data |= off_flags_wanted & ETH_FLAG_EXT_MASK;

			err = send_ioctl(ctx, &eval);
			if (err) {
				perror("Cannot set device flag settings");
				rc = 92;
				goto err;
			}
		}
	}

	/* Compare new state with requested state */
	new_state = get_features(ctx, defs);
	if (!new_state) {
		rc = 1;
		goto err;
	}
	any_changed = new_state->off_flags != old_state->off_flags;
	any_mismatch = (new_state->off_flags !=
			((old_state->off_flags & ~off_flags_mask) |
			 off_flags_wanted));
	for (i = 0; i < FEATURE_BITS_TO_BLOCKS(defs->n_features); i++) {
		if (new_state->features.features[i].active !=
		    old_state->features.features[i].active)
			any_changed = 1;
		if (new_state->features.features[i].active !=
		    ((old_state->features.features[i].active &
		      ~efeatures->features[i].valid) |
		     efeatures->features[i].requested))
			any_mismatch = 1;
	}
	if (any_mismatch) {
		if (!any_changed) {
			fprintf(stderr,
				"Could not change any device features\n");
			rc = 1;
			goto err;
		}
		printf("Actual changes:\n");
		dump_features(defs, new_state, old_state);
	}

	rc = 0;

err:
	free(new_state);
	free(old_state);
	free(defs);
	free(efeatures);

	return rc;
}

static struct ethtool_link_usettings *
do_ioctl_glinksettings(struct cmd_context *ctx)
{
	int err;
	struct {
		struct ethtool_link_settings req;
		__u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
	} ecmd;
	struct ethtool_link_usettings *link_usettings;
	unsigned int u32_offs;

	/* Handshake with kernel to determine number of words for link
	 * mode bitmaps. When requested number of bitmap words is not
	 * the one expected by kernel, the latter returns the integer
	 * opposite of what it is expecting. We request length 0 below
	 * (aka. invalid bitmap length) to get this info.
	 */
	memset(&ecmd, 0, sizeof(ecmd));
	ecmd.req.cmd = ETHTOOL_GLINKSETTINGS;
	err = send_ioctl(ctx, &ecmd);
	if (err < 0)
		return NULL;

	/* see above: we expect a strictly negative value from kernel.
	 */
	if (ecmd.req.link_mode_masks_nwords >= 0
	    || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
		return NULL;

	/* got the real ecmd.req.link_mode_masks_nwords,
	 * now send the real request
	 */
	ecmd.req.cmd = ETHTOOL_GLINKSETTINGS;
	ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords;
	err = send_ioctl(ctx, &ecmd);
	if (err < 0)
		return NULL;

	if (ecmd.req.link_mode_masks_nwords <= 0
	    || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
		return NULL;

	/* Convert to usettings struct */
	link_usettings = calloc(1, sizeof(*link_usettings));
	if (link_usettings == NULL)
		return NULL;

	memcpy(&link_usettings->base, &ecmd.req, sizeof(link_usettings->base));
	link_usettings->deprecated.transceiver = ecmd.req.transceiver;

	/* copy link mode bitmaps */
	u32_offs = 0;
	memcpy(link_usettings->link_modes.supported,
	       &ecmd.link_mode_data[u32_offs],
	       4 * ecmd.req.link_mode_masks_nwords);

	u32_offs += ecmd.req.link_mode_masks_nwords;
	memcpy(link_usettings->link_modes.advertising,
	       &ecmd.link_mode_data[u32_offs],
	       4 * ecmd.req.link_mode_masks_nwords);

	u32_offs += ecmd.req.link_mode_masks_nwords;
	memcpy(link_usettings->link_modes.lp_advertising,
	       &ecmd.link_mode_data[u32_offs],
	       4 * ecmd.req.link_mode_masks_nwords);

	return link_usettings;
}

static int
do_ioctl_slinksettings(struct cmd_context *ctx,
		       const struct ethtool_link_usettings *link_usettings)
{
	struct {
		struct ethtool_link_settings req;
		__u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
	} ecmd;
	unsigned int u32_offs;

	/* refuse to send ETHTOOL_SLINKSETTINGS ioctl if
	 * link_usettings was retrieved with ETHTOOL_GSET
	 */
	if (link_usettings->base.cmd != ETHTOOL_GLINKSETTINGS)
		return -1;

	/* refuse to send ETHTOOL_SLINKSETTINGS ioctl if deprecated fields
	 * were set
	 */
	if (link_usettings->deprecated.transceiver)
		return -1;

	if (link_usettings->base.link_mode_masks_nwords <= 0)
		return -1;

	memcpy(&ecmd.req, &link_usettings->base, sizeof(ecmd.req));
	ecmd.req.cmd = ETHTOOL_SLINKSETTINGS;

	/* copy link mode bitmaps */
	u32_offs = 0;
	memcpy(&ecmd.link_mode_data[u32_offs],
	       link_usettings->link_modes.supported,
	       4 * ecmd.req.link_mode_masks_nwords);

	u32_offs += ecmd.req.link_mode_masks_nwords;
	memcpy(&ecmd.link_mode_data[u32_offs],
	       link_usettings->link_modes.advertising,
	       4 * ecmd.req.link_mode_masks_nwords);

	u32_offs += ecmd.req.link_mode_masks_nwords;
	memcpy(&ecmd.link_mode_data[u32_offs],
	       link_usettings->link_modes.lp_advertising,
	       4 * ecmd.req.link_mode_masks_nwords);

	return send_ioctl(ctx, &ecmd);
}

static struct ethtool_link_usettings *
do_ioctl_gset(struct cmd_context *ctx)
{
	int err;
	struct ethtool_cmd ecmd;
	struct ethtool_link_usettings *link_usettings;

	memset(&ecmd, 0, sizeof(ecmd));
	ecmd.cmd = ETHTOOL_GSET;
	err = send_ioctl(ctx, &ecmd);
	if (err < 0)
		return NULL;

	link_usettings = calloc(1, sizeof(*link_usettings));
	if (link_usettings == NULL)
		return NULL;

	/* remember that ETHTOOL_GSET was used */
	link_usettings->base.cmd = ETHTOOL_GSET;

	link_usettings->base.link_mode_masks_nwords = 1;
	link_usettings->link_modes.supported[0] = ecmd.supported;
	link_usettings->link_modes.advertising[0] = ecmd.advertising;
	link_usettings->link_modes.lp_advertising[0] = ecmd.lp_advertising;
	link_usettings->base.speed = ethtool_cmd_speed(&ecmd);
	link_usettings->base.duplex = ecmd.duplex;
	link_usettings->base.port = ecmd.port;
	link_usettings->base.phy_address = ecmd.phy_address;
	link_usettings->deprecated.transceiver = ecmd.transceiver;
	link_usettings->base.autoneg = ecmd.autoneg;
	link_usettings->base.mdio_support = ecmd.mdio_support;
	/* ignored (fully deprecated): maxrxpkt, maxtxpkt */
	link_usettings->base.eth_tp_mdix = ecmd.eth_tp_mdix;
	link_usettings->base.eth_tp_mdix_ctrl = ecmd.eth_tp_mdix_ctrl;

	return link_usettings;
}

static bool ethtool_link_mode_is_backward_compatible(const u32 *mask)
{
	unsigned int i;

	for (i = 1; i < ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32; ++i)
		if (mask[i])
			return false;

	return true;
}

static int
do_ioctl_sset(struct cmd_context *ctx,
	      const struct ethtool_link_usettings *link_usettings)
{
	struct ethtool_cmd ecmd;

	/* refuse to send ETHTOOL_SSET ioctl if link_usettings was
	 * retrieved with ETHTOOL_GLINKSETTINGS
	 */
	if (link_usettings->base.cmd != ETHTOOL_GSET)
		return -1;

	if (link_usettings->base.link_mode_masks_nwords <= 0)
		return -1;

	/* refuse to sset if any bit > 31 is set */
	if (!ethtool_link_mode_is_backward_compatible(
		    link_usettings->link_modes.supported))
		return -1;
	if (!ethtool_link_mode_is_backward_compatible(
		    link_usettings->link_modes.advertising))
		return -1;
	if (!ethtool_link_mode_is_backward_compatible(
		    link_usettings->link_modes.lp_advertising))
		return -1;

	memset(&ecmd, 0, sizeof(ecmd));
	ecmd.cmd = ETHTOOL_SSET;

	ecmd.supported = link_usettings->link_modes.supported[0];
	ecmd.advertising = link_usettings->link_modes.advertising[0];
	ecmd.lp_advertising = link_usettings->link_modes.lp_advertising[0];
	ethtool_cmd_speed_set(&ecmd, link_usettings->base.speed);
	ecmd.duplex = link_usettings->base.duplex;
	ecmd.port = link_usettings->base.port;
	ecmd.phy_address = link_usettings->base.phy_address;
	ecmd.transceiver = link_usettings->deprecated.transceiver;
	ecmd.autoneg = link_usettings->base.autoneg;
	ecmd.mdio_support = link_usettings->base.mdio_support;
	/* ignored (fully deprecated): maxrxpkt, maxtxpkt */
	ecmd.eth_tp_mdix = link_usettings->base.eth_tp_mdix;
	ecmd.eth_tp_mdix_ctrl = link_usettings->base.eth_tp_mdix_ctrl;
	return send_ioctl(ctx, &ecmd);
}

static int do_gset(struct cmd_context *ctx)
{
	int err;
	struct ethtool_link_usettings *link_usettings;
	struct ethtool_wolinfo wolinfo;
	struct ethtool_value edata;
	int allfail = 1;

	if (ctx->argc != 0)
		exit_bad_args();

	fprintf(stdout, "Settings for %s:\n", ctx->devname);

	link_usettings = do_ioctl_glinksettings(ctx);
	if (link_usettings == NULL)
		link_usettings = do_ioctl_gset(ctx);
	if (link_usettings != NULL) {
		err = dump_link_usettings(link_usettings);
		free(link_usettings);
		if (err)
			return err;
		allfail = 0;
	} else if (errno != EOPNOTSUPP) {
		perror("Cannot get device settings");
	}

	wolinfo.cmd = ETHTOOL_GWOL;
	err = send_ioctl(ctx, &wolinfo);
	if (err == 0) {
		err = dump_wol(&wolinfo);
		if (err)
			return err;
		allfail = 0;
	} else if (errno != EOPNOTSUPP) {
		perror("Cannot get wake-on-lan settings");
	}

	edata.cmd = ETHTOOL_GMSGLVL;
	err = send_ioctl(ctx, &edata);
	if (err == 0) {
		fprintf(stdout, "	Current message level: 0x%08x (%d)\n"
			"			       ",
			edata.data, edata.data);
		print_flags(flags_msglvl, n_flags_msglvl, edata.data);
		fprintf(stdout, "\n");
		allfail = 0;
	} else if (errno != EOPNOTSUPP) {
		perror("Cannot get message level");
	}

	edata.cmd = ETHTOOL_GLINK;
	err = send_ioctl(ctx, &edata);
	if (err == 0) {
		fprintf(stdout, "	Link detected: %s\n",
			edata.data ? "yes":"no");
		allfail = 0;
	} else if (errno != EOPNOTSUPP) {
		perror("Cannot get link status");
	}

	if (allfail) {
		fprintf(stdout, "No data available\n");
		return 75;
	}
	return 0;
}

static int do_sset(struct cmd_context *ctx)
{
	int speed_wanted = -1;
	int duplex_wanted = -1;
	int port_wanted = -1;
	int mdix_wanted = -1;
	int autoneg_wanted = -1;
	int phyad_wanted = -1;
	int xcvr_wanted = -1;
	u32 *full_advertising_wanted = NULL;
	u32 *advertising_wanted = NULL;
	ETHTOOL_DECLARE_LINK_MODE_MASK(mask_full_advertising_wanted);
	ETHTOOL_DECLARE_LINK_MODE_MASK(mask_advertising_wanted);
	int gset_changed = 0; /* did anything in GSET change? */
	u32 wol_wanted = 0;
	int wol_change = 0;
	u8 sopass_wanted[SOPASS_MAX];
	int sopass_change = 0;
	int gwol_changed = 0; /* did anything in GWOL change? */
	int msglvl_changed = 0;
	u32 msglvl_wanted = 0;
	u32 msglvl_mask = 0;
	struct cmdline_info cmdline_msglvl[n_flags_msglvl];
	unsigned int argc = ctx->argc;
	char **argp = ctx->argp;
	unsigned int i;
	int err = 0;

	for (i = 0; i < n_flags_msglvl; i++)
		flag_to_cmdline_info(flags_msglvl[i].name,
				     flags_msglvl[i].value,
				     &msglvl_wanted, &msglvl_mask,
				     &cmdline_msglvl[i]);

	for (i = 0; i < argc; i++) {
		if (!strcmp(argp[i], "speed")) {
			gset_changed = 1;
			i += 1;
			if (i >= argc)
				exit_bad_args();
			speed_wanted = get_int(argp[i], 10);
		} else if (!strcmp(argp[i], "duplex")) {
			gset_changed = 1;
			i += 1;
			if (i >= argc)
				exit_bad_args();
			if (!strcmp(argp[i], "half"))
				duplex_wanted = DUPLEX_HALF;
			else if (!strcmp(argp[i], "full"))
				duplex_wanted = DUPLEX_FULL;
			else
				exit_bad_args();
		} else if (!strcmp(argp[i], "port")) {
			gset_changed = 1;
			i += 1;
			if (i >= argc)
				exit_bad_args();
			if (!strcmp(argp[i], "tp"))
				port_wanted = PORT_TP;
			else if (!strcmp(argp[i], "aui"))
				port_wanted = PORT_AUI;
			else if (!strcmp(argp[i], "bnc"))
				port_wanted = PORT_BNC;
			else if (!strcmp(argp[i], "mii"))
				port_wanted = PORT_MII;
			else if (!strcmp(argp[i], "fibre"))
				port_wanted = PORT_FIBRE;
			else
				exit_bad_args();
		} else if (!strcmp(argp[i], "mdix")) {
			gset_changed = 1;
			i += 1;
			if (i >= argc)
				exit_bad_args();
			if (!strcmp(argp[i], "auto"))
				mdix_wanted = ETH_TP_MDI_AUTO;
			else if (!strcmp(argp[i], "on"))
				mdix_wanted = ETH_TP_MDI_X;
			else if (!strcmp(argp[i], "off"))
				mdix_wanted = ETH_TP_MDI;
			else
				exit_bad_args();
		} else if (!strcmp(argp[i], "autoneg")) {
			i += 1;
			if (i >= argc)
				exit_bad_args();
			if (!strcmp(argp[i], "on")) {
				gset_changed = 1;
				autoneg_wanted = AUTONEG_ENABLE;
			} else if (!strcmp(argp[i], "off")) {
				gset_changed = 1;
				autoneg_wanted = AUTONEG_DISABLE;
			} else {
				exit_bad_args();
			}
		} else if (!strcmp(argp[i], "advertise")) {
			gset_changed = 1;
			i += 1;
			if (i >= argc)
				exit_bad_args();
			if (parse_hex_u32_bitmap(
				    argp[i],
				    ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBITS,
				    mask_full_advertising_wanted))
				exit_bad_args();
			full_advertising_wanted = mask_full_advertising_wanted;
		} else if (!strcmp(argp[i], "phyad")) {
			gset_changed = 1;
			i += 1;
			if (i >= argc)
				exit_bad_args();
			phyad_wanted = get_int(argp[i], 0);
		} else if (!strcmp(argp[i], "xcvr")) {
			gset_changed = 1;
			i += 1;
			if (i >= argc)
				exit_bad_args();
			if (!strcmp(argp[i], "internal"))
				xcvr_wanted = XCVR_INTERNAL;
			else if (!strcmp(argp[i], "external"))
				xcvr_wanted = XCVR_EXTERNAL;
			else
				exit_bad_args();
		} else if (!strcmp(argp[i], "wol")) {
			gwol_changed = 1;
			i++;
			if (i >= argc)
				exit_bad_args();
			if (parse_wolopts(argp[i], &wol_wanted) < 0)
				exit_bad_args();
			wol_change = 1;
		} else if (!strcmp(argp[i], "sopass")) {
			gwol_changed = 1;
			i++;
			if (i >= argc)
				exit_bad_args();
			get_mac_addr(argp[i], sopass_wanted);
			sopass_change = 1;
		} else if (!strcmp(argp[i], "msglvl")) {
			i++;
			if (i >= argc)
				exit_bad_args();
			if (isdigit((unsigned char)argp[i][0])) {
				msglvl_changed = 1;
				msglvl_mask = ~0;
				msglvl_wanted =
					get_uint_range(argp[i], 0,
						       0xffffffff);
			} else {
				ctx->argc -= i;
				ctx->argp += i;
				parse_generic_cmdline(
					ctx, &msglvl_changed,
					cmdline_msglvl,
					ARRAY_SIZE(cmdline_msglvl));
				break;
			}
		} else if (!strcmp(argp[i], "master-slave")) {
			exit_nlonly_param(argp[i]);
		} else {
			exit_bad_args();
		}
	}

	if (full_advertising_wanted == NULL) {
		/* User didn't supply a full advertisement bitfield:
		 * construct one from the specified speed and duplex.
		 */
		int adv_bit = -1;

		if (speed_wanted == SPEED_10 && duplex_wanted == DUPLEX_HALF)
			adv_bit = ETHTOOL_LINK_MODE_10baseT_Half_BIT;
		else if (speed_wanted == SPEED_10 &&
			 duplex_wanted == DUPLEX_FULL)
			adv_bit = ETHTOOL_LINK_MODE_10baseT_Full_BIT;
		else if (speed_wanted == SPEED_100 &&
			 duplex_wanted == DUPLEX_HALF)
			adv_bit = ETHTOOL_LINK_MODE_100baseT_Half_BIT;
		else if (speed_wanted == SPEED_100 &&
			 duplex_wanted == DUPLEX_FULL)
			adv_bit = ETHTOOL_LINK_MODE_100baseT_Full_BIT;
		else if (speed_wanted == SPEED_1000 &&
			 duplex_wanted == DUPLEX_HALF)
			adv_bit = ETHTOOL_LINK_MODE_1000baseT_Half_BIT;
		else if (speed_wanted == SPEED_1000 &&
			 duplex_wanted == DUPLEX_FULL)
			adv_bit = ETHTOOL_LINK_MODE_1000baseT_Full_BIT;
		else if (speed_wanted == SPEED_2500 &&
			 duplex_wanted == DUPLEX_FULL)
			adv_bit = ETHTOOL_LINK_MODE_2500baseX_Full_BIT;
		else if (speed_wanted == SPEED_10000 &&
			 duplex_wanted == DUPLEX_FULL)
			adv_bit = ETHTOOL_LINK_MODE_10000baseT_Full_BIT;

		if (adv_bit >= 0) {
			advertising_wanted = mask_advertising_wanted;
			ethtool_link_mode_zero(advertising_wanted);
			ethtool_link_mode_set_bit(
				adv_bit, advertising_wanted);
		}
		/* otherwise: auto negotiate without forcing,
		 * all supported speed will be assigned below
		 */
	}

	if (gset_changed) {
		struct ethtool_link_usettings *link_usettings;

		link_usettings = do_ioctl_glinksettings(ctx);
		if (link_usettings == NULL)
			link_usettings = do_ioctl_gset(ctx);
		else
			memset(&link_usettings->deprecated, 0,
			       sizeof(link_usettings->deprecated));
		if (link_usettings == NULL) {
			perror("Cannot get current device settings");
			err = -1;
		} else {
			/* Change everything the user specified. */
			if (speed_wanted != -1)
				link_usettings->base.speed = speed_wanted;
			if (duplex_wanted != -1)
				link_usettings->base.duplex = duplex_wanted;
			if (port_wanted != -1)
				link_usettings->base.port = port_wanted;
			if (mdix_wanted != -1) {
				/* check driver supports MDI-X */
				if (link_usettings->base.eth_tp_mdix_ctrl
				    != ETH_TP_MDI_INVALID)
					link_usettings->base.eth_tp_mdix_ctrl
						= mdix_wanted;
				else
					fprintf(stderr,
						"setting MDI not supported\n");
			}
			if (autoneg_wanted != -1)
				link_usettings->base.autoneg = autoneg_wanted;
			if (phyad_wanted != -1)
				link_usettings->base.phy_address = phyad_wanted;
			if (xcvr_wanted != -1)
				link_usettings->deprecated.transceiver
					= xcvr_wanted;
			/* XXX If the user specified speed or duplex
			 * then we should mask the advertised modes
			 * accordingly.  For now, warn that we aren't
			 * doing that.
			 */
			if ((speed_wanted != -1 || duplex_wanted != -1) &&
			    link_usettings->base.autoneg &&
			    advertising_wanted == NULL) {
				fprintf(stderr, "Cannot advertise");
				if (speed_wanted >= 0)
					fprintf(stderr, " speed %d",
						speed_wanted);
				if (duplex_wanted >= 0)
					fprintf(stderr, " duplex %s",
						duplex_wanted ?
						"full" : "half");
				fprintf(stderr,	"\n");
			}
			if (autoneg_wanted == AUTONEG_ENABLE &&
			    advertising_wanted == NULL &&
			    full_advertising_wanted == NULL) {
				unsigned int i;

				/* Auto negotiation enabled, but with
				 * unspecified speed and duplex: enable all
				 * supported speeds and duplexes.
				 */
				ethtool_link_mode_for_each_u32(i) {
					u32 sup = link_usettings->link_modes.supported[i];
					u32 *adv = link_usettings->link_modes.advertising + i;

					*adv = ((*adv & ~all_advertised_modes[i])
						| (sup & all_advertised_modes[i]));
				}

				/* If driver supports unknown flags, we cannot
				 * be sure that we enable all link modes.
				 */
				ethtool_link_mode_for_each_u32(i) {
					u32 sup = link_usettings->link_modes.supported[i];

					if ((sup & all_advertised_flags[i]) != sup) {
						fprintf(stderr, "Driver supports one or more unknown flags\n");
						break;
					}
				}
			} else if (advertising_wanted != NULL) {
				unsigned int i;

				/* Enable all requested modes */
				ethtool_link_mode_for_each_u32(i) {
					u32 *adv = link_usettings->link_modes.advertising + i;

					*adv = ((*adv & ~all_advertised_modes[i])
						| advertising_wanted[i]);
				}
			} else if (full_advertising_wanted != NULL) {
				ethtool_link_mode_copy(
					link_usettings->link_modes.advertising,
					full_advertising_wanted);
			}

			/* Try to perform the update. */
			if (link_usettings->base.cmd == ETHTOOL_GLINKSETTINGS)
				err = do_ioctl_slinksettings(ctx,
							     link_usettings);
			else
				err = do_ioctl_sset(ctx, link_usettings);
			free(link_usettings);
			if (err < 0)
				perror("Cannot set new settings");
		}
		if (err < 0) {
			if (speed_wanted != -1)
				fprintf(stderr, "  not setting speed\n");
			if (duplex_wanted != -1)
				fprintf(stderr, "  not setting duplex\n");
			if (port_wanted != -1)
				fprintf(stderr, "  not setting port\n");
			if (autoneg_wanted != -1)
				fprintf(stderr, "  not setting autoneg\n");
			if (phyad_wanted != -1)
				fprintf(stderr, "  not setting phy_address\n");
			if (xcvr_wanted != -1)
				fprintf(stderr, "  not setting transceiver\n");
			if (mdix_wanted != -1)
				fprintf(stderr, "  not setting mdix\n");
		}
	}

	if (gwol_changed) {
		struct ethtool_wolinfo wol;

		wol.cmd = ETHTOOL_GWOL;
		err = send_ioctl(ctx, &wol);
		if (err < 0) {
			perror("Cannot get current wake-on-lan settings");
		} else {
			/* Change everything the user specified. */
			if (wol_change)
				wol.wolopts = wol_wanted;
			if (sopass_change) {
				int i;
				for (i = 0; i < SOPASS_MAX; i++)
					wol.sopass[i] = sopass_wanted[i];
			}

			/* Try to perform the update. */
			wol.cmd = ETHTOOL_SWOL;
			err = send_ioctl(ctx, &wol);
			if (err < 0)
				perror("Cannot set new wake-on-lan settings");
		}
		if (err < 0) {
			if (wol_change)
				fprintf(stderr, "  not setting wol\n");
			if (sopass_change)
				fprintf(stderr, "  not setting sopass\n");
		}
	}

	if (msglvl_changed) {
		struct ethtool_value edata;

		edata.cmd = ETHTOOL_GMSGLVL;
		err = send_ioctl(ctx, &edata);
		if (err < 0) {
			perror("Cannot get msglvl");
		} else {
			edata.cmd = ETHTOOL_SMSGLVL;
			edata.data = ((edata.data & ~msglvl_mask) |
				      msglvl_wanted);
			err = send_ioctl(ctx, &edata);
			if (err < 0)
				perror("Cannot set new msglvl");
		}
	}

	return 0;
}

static int do_gregs(struct cmd_context *ctx)
{
	int gregs_changed = 0;
	int gregs_dump_raw = 0;
	int gregs_dump_hex = 0;
	char *gregs_dump_file = NULL;
	struct cmdline_info cmdline_gregs[] = {
		{
			.name		= "raw",
			.type		= CMDL_BOOL,
			.wanted_val	= &gregs_dump_raw,
		},
		{
			.name		= "hex",
			.type		= CMDL_BOOL,
			.wanted_val	= &gregs_dump_hex,
		},
		{
			.name		= "file",
			.type		= CMDL_STR,
			.wanted_val	= &gregs_dump_file,
		},
	};
	int err;
	struct ethtool_drvinfo drvinfo;
	struct ethtool_regs *regs;

	parse_generic_cmdline(ctx, &gregs_changed,
			      cmdline_gregs, ARRAY_SIZE(cmdline_gregs));

	drvinfo.cmd = ETHTOOL_GDRVINFO;
	err = send_ioctl(ctx, &drvinfo);
	if (err < 0) {
		perror("Cannot get driver information");
		return 72;
	}

	regs = calloc(1, sizeof(*regs)+drvinfo.regdump_len);
	if (!regs) {
		perror("Cannot allocate memory for register dump");
		return 73;
	}
	regs->cmd = ETHTOOL_GREGS;
	regs->len = drvinfo.regdump_len;
	err = send_ioctl(ctx, regs);
	if (err < 0) {
		perror("Cannot get register dump");
		free(regs);
		return 74;
	}

	if (!gregs_dump_raw && gregs_dump_file != NULL) {
		/* overwrite reg values from file dump */
		FILE *f = fopen(gregs_dump_file, "r");
		struct ethtool_regs *nregs;
		struct stat st;
		size_t nread;

		if (!f || fstat(fileno(f), &st) < 0) {
			fprintf(stderr, "Can't open '%s': %s\n",
				gregs_dump_file, strerror(errno));
			if (f)
				fclose(f);
			free(regs);
			return 75;
		}

		nregs = realloc(regs, sizeof(*regs) + st.st_size);
		if (!nregs) {
			perror("Cannot allocate memory for register dump");
			free(regs); /* was not freed by realloc */
			return 73;
		}
		regs = nregs;
		regs->len = st.st_size;
		nread = fread(regs->data, regs->len, 1, f);
		fclose(f);
		if (nread != 1) {
			free(regs);
			return 75;
		}
	}

	if (dump_regs(gregs_dump_raw, gregs_dump_hex,
		      &drvinfo, regs) < 0) {
		fprintf(stderr, "Cannot dump registers\n");
		free(regs);
		return 75;
	}
	free(regs);

	return 0;
}

static int do_nway_rst(struct cmd_context *ctx)
{
	struct ethtool_value edata;
	int err;

	if (ctx->argc != 0)
		exit_bad_args();

	edata.cmd = ETHTOOL_NWAY_RST;
	err = send_ioctl(ctx, &edata);
	if (err < 0)
		perror("Cannot restart autonegotiation");

	return err;
}

static int do_geeprom(struct cmd_context *ctx)
{
	int geeprom_changed = 0;
	int geeprom_dump_raw = 0;
	u32 geeprom_offset = 0;
	u32 geeprom_length = 0;
	int geeprom_length_seen = 0;
	struct cmdline_info cmdline_geeprom[] = {
		{
			.name		= "offset",
			.type		= CMDL_U32,
			.wanted_val	= &geeprom_offset,
		},
		{
			.name		= "length",
			.type		= CMDL_U32,
			.wanted_val	= &geeprom_length,
			.seen_val	= &geeprom_length_seen,
		},
		{
			.name		= "raw",
			.type		= CMDL_BOOL,
			.wanted_val	= &geeprom_dump_raw,
		},
	};
	int err;
	struct ethtool_drvinfo drvinfo;
	struct ethtool_eeprom *eeprom;

	parse_generic_cmdline(ctx, &geeprom_changed,
			      cmdline_geeprom, ARRAY_SIZE(cmdline_geeprom));

	drvinfo.cmd = ETHTOOL_GDRVINFO;
	err = send_ioctl(ctx, &drvinfo);
	if (err < 0) {
		perror("Cannot get driver information");
		return 74;
	}

	if (!geeprom_length_seen)
		geeprom_length = drvinfo.eedump_len;

	if (drvinfo.eedump_len < geeprom_offset + geeprom_length)
		geeprom_length = drvinfo.eedump_len - geeprom_offset;

	eeprom = calloc(1, sizeof(*eeprom)+geeprom_length);
	if (!eeprom) {
		perror("Cannot allocate memory for EEPROM data");
		return 75;
	}
	eeprom->cmd = ETHTOOL_GEEPROM;
	eeprom->len = geeprom_length;
	eeprom->offset = geeprom_offset;
	err = send_ioctl(ctx, eeprom);
	if (err < 0) {
		perror("Cannot get EEPROM data");
		free(eeprom);
		return 74;
	}
	err = dump_eeprom(geeprom_dump_raw, &drvinfo, eeprom);
	free(eeprom);

	return err;
}

static int do_seeprom(struct cmd_context *ctx)
{
	int seeprom_changed = 0;
	u32 seeprom_magic = 0;
	u32 seeprom_length = 0;
	u32 seeprom_offset = 0;
	u8 seeprom_value = 0;
	int seeprom_length_seen = 0;
	int seeprom_value_seen = 0;
	struct cmdline_info cmdline_seeprom[] = {
		{
			.name		= "magic",
			.type		= CMDL_U32,
			.wanted_val	= &seeprom_magic,
		},
		{
			.name		= "offset",
			.type		= CMDL_U32,
			.wanted_val	= &seeprom_offset,
		},
		{
			.name		= "length",
			.type		= CMDL_U32,
			.wanted_val	= &seeprom_length,
			.seen_val	= &seeprom_length_seen,
		},
		{
			.name		= "value",
			.type		= CMDL_U8,
			.wanted_val	= &seeprom_value,
			.seen_val	= &seeprom_value_seen,
		},
	};
	int err;
	struct ethtool_drvinfo drvinfo;
	struct ethtool_eeprom *eeprom;

	parse_generic_cmdline(ctx, &seeprom_changed,
			      cmdline_seeprom, ARRAY_SIZE(cmdline_seeprom));

	drvinfo.cmd = ETHTOOL_GDRVINFO;
	err = send_ioctl(ctx, &drvinfo);
	if (err < 0) {
		perror("Cannot get driver information");
		return 74;
	}

	if (seeprom_value_seen && !seeprom_length_seen)
		seeprom_length = 1;
	else if (!seeprom_length_seen)
		seeprom_length = drvinfo.eedump_len;

	if (seeprom_value_seen && (seeprom_length != 1)) {
		fprintf(stderr, "value requires length 1\n");
		return 1;
	}

	if (drvinfo.eedump_len < seeprom_offset + seeprom_length) {
		fprintf(stderr, "offset & length out of bounds\n");
		return 1;
	}

	eeprom = calloc(1, sizeof(*eeprom)+seeprom_length);
	if (!eeprom) {
		perror("Cannot allocate memory for EEPROM data");
		return 75;
	}

	eeprom->cmd = ETHTOOL_SEEPROM;
	eeprom->len = seeprom_length;
	eeprom->offset = seeprom_offset;
	eeprom->magic = seeprom_magic;
	eeprom->data[0] = seeprom_value;

	/* Multi-byte write: read input from stdin */
	if (!seeprom_value_seen) {
		if (fread(eeprom->data, eeprom->len, 1, stdin) != 1) {
			fprintf(stderr, "not enough data from stdin\n");
			free(eeprom);
			return 75;
		}
		if ((fgetc(stdin) != EOF) || !feof(stdin)) {
			fprintf(stderr, "too much data from stdin\n");
			free(eeprom);
			return 75;
		}
	}

	err = send_ioctl(ctx, eeprom);
	if (err < 0) {
		perror("Cannot set EEPROM data");
		err = 87;
	}
	free(eeprom);

	return err;
}

static int do_test(struct cmd_context *ctx)
{
	enum {
		ONLINE = 0,
		OFFLINE,
		EXTERNAL_LB,
	} test_type;
	int err;
	struct ethtool_test *test;
	struct ethtool_gstrings *strings;

	if (ctx->argc > 1)
		exit_bad_args();
	if (ctx->argc == 1) {
		if (!strcmp(ctx->argp[0], "online"))
			test_type = ONLINE;
		else if (!strcmp(*ctx->argp, "offline"))
			test_type = OFFLINE;
		else if (!strcmp(*ctx->argp, "external_lb"))
			test_type = EXTERNAL_LB;
		else
			exit_bad_args();
	} else {
		test_type = OFFLINE;
	}

	strings = get_stringset(ctx, ETH_SS_TEST,
				offsetof(struct ethtool_drvinfo, testinfo_len),
				1);
	if (!strings) {
		perror("Cannot get strings");
		return 74;
	}

	test = calloc(1, sizeof(*test) + strings->len * sizeof(u64));
	if (!test) {
		perror("Cannot allocate memory for test info");
		free(strings);
		return 73;
	}
	memset(test->data, 0, strings->len * sizeof(u64));
	test->cmd = ETHTOOL_TEST;
	test->len = strings->len;
	if (test_type == EXTERNAL_LB)
		test->flags = (ETH_TEST_FL_OFFLINE | ETH_TEST_FL_EXTERNAL_LB);
	else if (test_type == OFFLINE)
		test->flags = ETH_TEST_FL_OFFLINE;
	else
		test->flags = 0;
	err = send_ioctl(ctx, test);
	if (err < 0) {
		perror("Cannot test");
		free(test);
		free(strings);
		return 74;
	}

	err = dump_test(test, strings);
	free(test);
	free(strings);

	return err;
}

static int do_phys_id(struct cmd_context *ctx)
{
	int err;
	struct ethtool_value edata;
	int phys_id_time;

	if (ctx->argc > 1)
		exit_bad_args();
	if (ctx->argc == 1)
		phys_id_time = get_int(*ctx->argp, 0);
	else
		phys_id_time = 0;

	edata.cmd = ETHTOOL_PHYS_ID;
	edata.data = phys_id_time;
	err = send_ioctl(ctx, &edata);
	if (err < 0)
		perror("Cannot identify NIC");

	return err;
}

static int do_gstats(struct cmd_context *ctx, int cmd, int stringset,
		    const char *name)
{
	struct ethtool_gstrings *strings;
	struct ethtool_stats *stats;
	unsigned int n_stats, sz_stats, i;
	int err;

	if (ctx->argc != 0)
		exit_bad_args();

	strings = get_stringset(ctx, stringset,
				offsetof(struct ethtool_drvinfo, n_stats),
				0);
	if (!strings) {
		perror("Cannot get stats strings information");
		return 96;
	}

	n_stats = strings->len;
	if (n_stats < 1) {
		fprintf(stderr, "no stats available\n");
		free(strings);
		return 94;
	}

	sz_stats = n_stats * sizeof(u64);

	stats = calloc(1, sz_stats + sizeof(struct ethtool_stats));
	if (!stats) {
		fprintf(stderr, "no memory available\n");
		free(strings);
		return 95;
	}

	stats->cmd = cmd;
	stats->n_stats = n_stats;
	err = send_ioctl(ctx, stats);
	if (err < 0) {
		perror("Cannot get stats information");
		free(strings);
		free(stats);
		return 97;
	}

	/* todo - pretty-print the strings per-driver */
	fprintf(stdout, "%s statistics:\n", name);
	for (i = 0; i < n_stats; i++) {
		fprintf(stdout, "     %.*s: %llu\n",
			ETH_GSTRING_LEN,
			&strings->data[i * ETH_GSTRING_LEN],
			stats->data[i]);
	}
	free(strings);
	free(stats);

	return 0;
}

static int do_gnicstats(struct cmd_context *ctx)
{
	return do_gstats(ctx, ETHTOOL_GSTATS, ETH_SS_STATS, "NIC");
}

static int do_gphystats(struct cmd_context *ctx)
{
	return do_gstats(ctx, ETHTOOL_GPHYSTATS, ETH_SS_PHY_STATS, "PHY");
}

static int do_srxntuple(struct cmd_context *ctx,
			struct ethtool_rx_flow_spec *rx_rule_fs);

static int do_srxclass(struct cmd_context *ctx)
{
	int err;

	if (ctx->argc < 2)
		exit_bad_args();

	if (!strcmp(ctx->argp[0], "rx-flow-hash")) {
		int rx_fhash_set;
		u32 rx_fhash_val;
		struct ethtool_rxnfc nfccmd;
		bool flow_rss = false;

		if (ctx->argc == 5) {
			if (strcmp(ctx->argp[3], "context"))
				exit_bad_args();
			flow_rss = true;
			nfccmd.rss_context = get_u32(ctx->argp[4], 0);
		} else if (ctx->argc != 3) {
			exit_bad_args();
		}
		rx_fhash_set = rxflow_str_to_type(ctx->argp[1]);
		if (!rx_fhash_set)
			exit_bad_args();
		if (parse_rxfhashopts(ctx->argp[2], &rx_fhash_val) < 0)
			exit_bad_args();

		nfccmd.cmd = ETHTOOL_SRXFH;
		nfccmd.flow_type = rx_fhash_set;
		nfccmd.data = rx_fhash_val;
		if (flow_rss)
			nfccmd.flow_type |= FLOW_RSS;

		err = send_ioctl(ctx, &nfccmd);
		if (err < 0)
			perror("Cannot change RX network flow hashing options");
	} else if (!strcmp(ctx->argp[0], "flow-type")) {
		struct ethtool_rx_flow_spec rx_rule_fs;
		__u32 rss_context = 0;

		ctx->argc--;
		ctx->argp++;
		if (rxclass_parse_ruleopts(ctx, &rx_rule_fs, &rss_context) < 0)
			exit_bad_args();

		/* attempt to add rule via N-tuple specifier */
		err = do_srxntuple(ctx, &rx_rule_fs);
		if (!err)
			return 0;

		/* attempt to add rule via network flow classifier */
		err = rxclass_rule_ins(ctx, &rx_rule_fs, rss_context);
		if (err < 0) {
			fprintf(stderr, "Cannot insert"
				" classification rule\n");
			return 1;
		}
	} else if (!strcmp(ctx->argp[0], "delete")) {
		int rx_class_rule_del =
			get_uint_range(ctx->argp[1], 0, INT_MAX);

		err = rxclass_rule_del(ctx, rx_class_rule_del);

		if (err < 0) {
			fprintf(stderr, "Cannot delete"
				" classification rule\n");
			return 1;
		}
	} else {
		exit_bad_args();
	}

	return 0;
}

static int do_grxclass(struct cmd_context *ctx)
{
	struct ethtool_rxnfc nfccmd;
	int err;

	if (ctx->argc > 0 && !strcmp(ctx->argp[0], "rx-flow-hash")) {
		int rx_fhash_get;
		bool flow_rss = false;

		if (ctx->argc == 4) {
			if (strcmp(ctx->argp[2], "context"))
				exit_bad_args();
			flow_rss = true;
			nfccmd.rss_context = get_u32(ctx->argp[3], 0);
		} else if (ctx->argc != 2) {
			exit_bad_args();
		}

		rx_fhash_get = rxflow_str_to_type(ctx->argp[1]);
		if (!rx_fhash_get)
			exit_bad_args();

		nfccmd.cmd = ETHTOOL_GRXFH;
		nfccmd.flow_type = rx_fhash_get;
		if (flow_rss)
			nfccmd.flow_type |= FLOW_RSS;
		err = send_ioctl(ctx, &nfccmd);
		if (err < 0) {
			perror("Cannot get RX network flow hashing options");
		} else {
			if (flow_rss)
				fprintf(stdout, "For RSS context %u:\n",
					nfccmd.rss_context);
			dump_rxfhash(rx_fhash_get, nfccmd.data);
		}
	} else if (ctx->argc == 2 && !strcmp(ctx->argp[0], "rule")) {
		int rx_class_rule_get =
			get_uint_range(ctx->argp[1], 0, INT_MAX);

		err = rxclass_rule_get(ctx, rx_class_rule_get);
		if (err < 0)
			fprintf(stderr, "Cannot get RX classification rule\n");
	} else if (ctx->argc == 0) {
		nfccmd.cmd = ETHTOOL_GRXRINGS;
		err = send_ioctl(ctx, &nfccmd);
		if (err < 0)
			perror("Cannot get RX rings");
		else
			fprintf(stdout, "%d RX rings available\n",
				(int)nfccmd.data);

		err = rxclass_rule_getall(ctx);
		if (err < 0)
			fprintf(stderr, "RX classification rule retrieval failed\n");

	} else {
		exit_bad_args();
	}

	return err ? 1 : 0;
}

static int do_grxfhindir(struct cmd_context *ctx,
			 struct ethtool_rxnfc *ring_count)
{
	struct ethtool_rxfh_indir indir_head;
	struct ethtool_rxfh_indir *indir;
	int err;

	indir_head.cmd = ETHTOOL_GRXFHINDIR;
	indir_head.size = 0;
	err = send_ioctl(ctx, &indir_head);
	if (err < 0) {
		perror("Cannot get RX flow hash indirection table size");
		return 1;
	}

	indir = malloc(sizeof(*indir) +
		       indir_head.size * sizeof(*indir->ring_index));
	if (!indir) {
		perror("Cannot allocate memory for indirection table");
		return 1;
	}

	indir->cmd = ETHTOOL_GRXFHINDIR;
	indir->size = indir_head.size;
	err = send_ioctl(ctx, indir);
	if (err < 0) {
		perror("Cannot get RX flow hash indirection table");
		free(indir);
		return 1;
	}

	print_indir_table(ctx, ring_count->data, indir->size,
			  indir->ring_index);

	free(indir);
	return 0;
}

static int do_grxfh(struct cmd_context *ctx)
{
	struct ethtool_gstrings *hfuncs = NULL;
	struct ethtool_rxfh rss_head = {0};
	struct ethtool_rxnfc ring_count;
	struct ethtool_rxfh *rss;
	u32 rss_context = 0;
	u32 i, indir_bytes;
	unsigned int arg_num = 0;
	u8 *hkey;
	int err;

	while (arg_num < ctx->argc) {
		if (!strcmp(ctx->argp[arg_num], "context")) {
			++arg_num;
			rss_context = get_int_range(ctx->argp[arg_num], 0, 1,
						    ETH_RXFH_CONTEXT_ALLOC - 1);
			++arg_num;
		} else {
			exit_bad_args();
		}
	}

	ring_count.cmd = ETHTOOL_GRXRINGS;
	err = send_ioctl(ctx, &ring_count);
	if (err < 0) {
		perror("Cannot get RX ring count");
		return 1;
	}

	rss_head.cmd = ETHTOOL_GRSSH;
	rss_head.rss_context = rss_context;
	err = send_ioctl(ctx, &rss_head);
	if (err < 0 && errno == EOPNOTSUPP && !rss_context) {
		return do_grxfhindir(ctx, &ring_count);
	} else if (err < 0) {
		perror("Cannot get RX flow hash indir size and/or key size");
		return 1;
	}

	rss = calloc(1, sizeof(*rss) +
			rss_head.indir_size * sizeof(rss_head.rss_config[0]) +
			rss_head.key_size);
	if (!rss) {
		perror("Cannot allocate memory for RX flow hash config");
		return 1;
	}

	rss->cmd = ETHTOOL_GRSSH;
	rss->rss_context = rss_context;
	rss->indir_size = rss_head.indir_size;
	rss->key_size = rss_head.key_size;
	err = send_ioctl(ctx, rss);
	if (err < 0) {
		perror("Cannot get RX flow hash configuration");
		free(rss);
		return 1;
	}

	print_indir_table(ctx, ring_count.data, rss->indir_size,
			  rss->rss_config);

	indir_bytes = rss->indir_size * sizeof(rss->rss_config[0]);
	hkey = ((u8 *)rss->rss_config + indir_bytes);

	print_rss_hkey(hkey, rss->key_size);

	printf("RSS hash function:\n");
	if (!rss->hfunc) {
		printf("    Operation not supported\n");
		goto out;
	}

	hfuncs = get_stringset(ctx, ETH_SS_RSS_HASH_FUNCS, 0, 1);
	if (!hfuncs) {
		perror("Cannot get hash functions names");
		free(rss);
		return 1;
	}

	for (i = 0; i < hfuncs->len; i++)
		printf("    %s: %s\n",
		       (const char *)hfuncs->data + i * ETH_GSTRING_LEN,
		       (rss->hfunc & (1 << i)) ? "on" : "off");

out:
	free(hfuncs);
	free(rss);
	return 0;
}

static int fill_indir_table(u32 *indir_size, u32 *indir, int rxfhindir_default,
			    int rxfhindir_start, int rxfhindir_equal,
			    char **rxfhindir_weight, u32 num_weights)
{
	u32 i;

	if (rxfhindir_equal) {
		for (i = 0; i < *indir_size; i++)
			indir[i] = rxfhindir_start + (i % rxfhindir_equal);
	} else if (rxfhindir_weight) {
		u32 j, weight, sum = 0, partial = 0;

		for (j = 0; j < num_weights; j++) {
			weight = get_u32(rxfhindir_weight[j], 0);
			sum += weight;
		}

		if (sum == 0) {
			fprintf(stderr,
				"At least one weight must be non-zero\n");
			return 2;
		}

		if (sum > *indir_size) {
			fprintf(stderr,
				"Total weight exceeds the size of the "
				"indirection table\n");
			return 2;
		}

		j = -1;
		for (i = 0; i < *indir_size; i++) {
			while (i >= (*indir_size) * partial / sum) {
				j += 1;
				weight = get_u32(rxfhindir_weight[j], 0);
				partial += weight;
			}
			indir[i] = rxfhindir_start + j;
		}
	} else if (rxfhindir_default) {
		/* "*indir_size == 0" ==> reset indir to default */
		*indir_size = 0;
	} else {
		*indir_size = ETH_RXFH_INDIR_NO_CHANGE;
	}

	return 0;
}

static int do_srxfhindir(struct cmd_context *ctx, int rxfhindir_default,
			 int rxfhindir_start, int rxfhindir_equal,
			 char **rxfhindir_weight, u32 num_weights)
{
	struct ethtool_rxfh_indir indir_head;
	struct ethtool_rxfh_indir *indir;
	int err;

	indir_head.cmd = ETHTOOL_GRXFHINDIR;
	indir_head.size = 0;
	err = send_ioctl(ctx, &indir_head);
	if (err < 0) {
		perror("Cannot get RX flow hash indirection table size");
		return 1;
	}

	indir = malloc(sizeof(*indir) +
		       indir_head.size * sizeof(*indir->ring_index));

	if (!indir) {
		perror("Cannot allocate memory for indirection table");
		return 1;
	}

	indir->cmd = ETHTOOL_SRXFHINDIR;
	indir->size = indir_head.size;

	if (fill_indir_table(&indir->size, indir->ring_index,
			     rxfhindir_default, rxfhindir_start,
			     rxfhindir_equal, rxfhindir_weight, num_weights)) {
		free(indir);
		return 1;
	}

	err = send_ioctl(ctx, indir);
	if (err < 0) {
		perror("Cannot set RX flow hash indirection table");
		free(indir);
		return 1;
	}

	free(indir);
	return 0;
}

static int do_srxfh(struct cmd_context *ctx)
{
	struct ethtool_rxfh rss_head = {0};
	struct ethtool_rxfh *rss = NULL;
	struct ethtool_rxnfc ring_count;
	int rxfhindir_equal = 0, rxfhindir_default = 0, rxfhindir_start = 0;
	struct ethtool_gstrings *hfuncs = NULL;
	char **rxfhindir_weight = NULL;
	char *rxfhindir_key = NULL;
	char *req_hfunc_name = NULL;
	char *hfunc_name = NULL;
	char *hkey = NULL;
	int err = 0;
	unsigned int i;
	u32 arg_num = 0, indir_bytes = 0;
	u32 req_hfunc = 0;
	u32 entry_size = sizeof(rss_head.rss_config[0]);
	u32 num_weights = 0;
	u32 rss_context = 0;
	int delete = 0;

	if (ctx->argc < 1)
		exit_bad_args();

	while (arg_num < ctx->argc) {
		if (!strcmp(ctx->argp[arg_num], "equal")) {
			++arg_num;
			rxfhindir_equal = get_int_range(ctx->argp[arg_num],
							0, 1, INT_MAX);
			++arg_num;
		} else if (!strcmp(ctx->argp[arg_num], "start")) {
			++arg_num;
			rxfhindir_start = get_int_range(ctx->argp[arg_num],
							0, 0, INT_MAX);
			++arg_num;
		} else if (!strcmp(ctx->argp[arg_num], "weight")) {
			++arg_num;
			rxfhindir_weight = ctx->argp + arg_num;
			while (arg_num < ctx->argc &&
			       isdigit((unsigned char)ctx->argp[arg_num][0])) {
				++arg_num;
				++num_weights;
			}
			if (!num_weights)
				exit_bad_args();
		} else if (!strcmp(ctx->argp[arg_num], "hkey")) {
			++arg_num;
			rxfhindir_key = ctx->argp[arg_num];
			if (!rxfhindir_key)
				exit_bad_args();
			++arg_num;
		} else if (!strcmp(ctx->argp[arg_num], "default")) {
			++arg_num;
			rxfhindir_default = 1;
		} else if (!strcmp(ctx->argp[arg_num], "hfunc")) {
			++arg_num;
			req_hfunc_name = ctx->argp[arg_num];
			if (!req_hfunc_name)
				exit_bad_args();
			++arg_num;
		} else if (!strcmp(ctx->argp[arg_num], "context")) {
			++arg_num;
			if(!strcmp(ctx->argp[arg_num], "new"))
				rss_context = ETH_RXFH_CONTEXT_ALLOC;
			else
				rss_context = get_int_range(
						ctx->argp[arg_num], 0, 1,
						ETH_RXFH_CONTEXT_ALLOC - 1);
			++arg_num;
		} else if (!strcmp(ctx->argp[arg_num], "delete")) {
			++arg_num;
			delete = 1;
		} else {
			exit_bad_args();
		}
	}

	if (rxfhindir_equal && rxfhindir_weight) {
		fprintf(stderr,
			"Equal and weight options are mutually exclusive\n");
		return 1;
	}

	if (rxfhindir_equal && rxfhindir_default) {
		fprintf(stderr,
			"Equal and default options are mutually exclusive\n");
		return 1;
	}

	if (rxfhindir_weight && rxfhindir_default) {
		fprintf(stderr,
			"Weight and default options are mutually exclusive\n");
		return 1;
	}

	if (rxfhindir_start && rxfhindir_default) {
		fprintf(stderr,
			"Start and default options are mutually exclusive\n");
		return 1;
	}

	if (rxfhindir_start && !(rxfhindir_equal || rxfhindir_weight)) {
		fprintf(stderr,
			"Start must be used with equal or weight options\n");
		return 1;
	}

	if (rxfhindir_default && rss_context) {
		fprintf(stderr,
			"Default and context options are mutually exclusive\n");
		return 1;
	}

	if (delete && !rss_context) {
		fprintf(stderr, "Delete option requires context option\n");
		return 1;
	}

	if (delete && rxfhindir_weight) {
		fprintf(stderr,
			"Delete and weight options are mutually exclusive\n");
		return 1;
	}

	if (delete && rxfhindir_equal) {
		fprintf(stderr,
			"Delete and equal options are mutually exclusive\n");
		return 1;
	}

	if (delete && rxfhindir_default) {
		fprintf(stderr,
			"Delete and default options are mutually exclusive\n");
		return 1;
	}

	if (delete && rxfhindir_key) {
		fprintf(stderr,
			"Delete and hkey options are mutually exclusive\n");
		return 1;
	}

	ring_count.cmd = ETHTOOL_GRXRINGS;
	err = send_ioctl(ctx, &ring_count);
	if (err < 0) {
		perror("Cannot get RX ring count");
		return 1;
	}

	rss_head.cmd = ETHTOOL_GRSSH;
	err = send_ioctl(ctx, &rss_head);
	if (err < 0 && errno == EOPNOTSUPP && !rxfhindir_key &&
	    !req_hfunc_name && !rss_context) {
		return do_srxfhindir(ctx, rxfhindir_default, rxfhindir_start,
				     rxfhindir_equal, rxfhindir_weight,
				     num_weights);
	} else if (err < 0) {
		perror("Cannot get RX flow hash indir size and key size");
		return 1;
	}

	if (rxfhindir_key) {
		err = parse_hkey(&hkey, rss_head.key_size,
				 rxfhindir_key);
		if (err)
			return err;
	}

	if (rxfhindir_equal || rxfhindir_weight)
		indir_bytes = rss_head.indir_size * entry_size;

	if (rss_head.hfunc && req_hfunc_name) {
		hfuncs = get_stringset(ctx, ETH_SS_RSS_HASH_FUNCS, 0, 1);
		if (!hfuncs) {
			perror("Cannot get hash functions names");
			err = 1;
			goto free;
		}

		for (i = 0; i < hfuncs->len && !req_hfunc ; i++) {
			hfunc_name = (char *)(hfuncs->data +
					      i * ETH_GSTRING_LEN);
			if (!strncmp(hfunc_name, req_hfunc_name,
				     ETH_GSTRING_LEN))
				req_hfunc = (u32)1 << i;
		}

		if (!req_hfunc) {
			fprintf(stderr,
				"Unknown hash function: %s\n", req_hfunc_name);
			err = 1;
			goto free;
		}
	}

	rss = calloc(1, sizeof(*rss) + indir_bytes + rss_head.key_size);
	if (!rss) {
		perror("Cannot allocate memory for RX flow hash config");
		err = 1;
		goto free;
	}
	rss->cmd = ETHTOOL_SRSSH;
	rss->rss_context = rss_context;
	rss->hfunc = req_hfunc;
	if (delete) {
		rss->indir_size = rss->key_size = 0;
	} else {
		rss->indir_size = rss_head.indir_size;
		rss->key_size = rss_head.key_size;
		if (fill_indir_table(&rss->indir_size, rss->rss_config,
				     rxfhindir_default, rxfhindir_start,
				     rxfhindir_equal, rxfhindir_weight,
				     num_weights)) {
			err = 1;
			goto free;
		}
	}

	if (hkey)
		memcpy((char *)rss->rss_config + indir_bytes,
		       hkey, rss->key_size);
	else
		rss->key_size = 0;

	err = send_ioctl(ctx, rss);
	if (err < 0) {
		perror("Cannot set RX flow hash configuration");
		err = 1;
	} else if (rss_context == ETH_RXFH_CONTEXT_ALLOC) {
		printf("New RSS context is %d\n", rss->rss_context);
	}

free:
	free(hkey);
	free(rss);
	free(hfuncs);
	return err;
}

static int do_flash(struct cmd_context *ctx)
{
	char *flash_file;
	int flash_region;
	struct ethtool_flash efl;
	int err;

	if (ctx->argc < 1 || ctx->argc > 2)
		exit_bad_args();
	flash_file = ctx->argp[0];
	if (ctx->argc == 2) {
		flash_region = strtol(ctx->argp[1], NULL, 0);
		if (flash_region < 0)
			exit_bad_args();
	} else {
		flash_region = -1;
	}

	if (strlen(flash_file) > ETHTOOL_FLASH_MAX_FILENAME - 1) {
		fprintf(stdout, "Filename too long\n");
		return 99;
	}

	efl.cmd = ETHTOOL_FLASHDEV;
	strcpy(efl.data, flash_file);

	if (flash_region < 0)
		efl.region = ETHTOOL_FLASH_ALL_REGIONS;
	else
		efl.region = flash_region;

	err = send_ioctl(ctx, &efl);
	if (err < 0)
		perror("Flashing failed");

	return err;
}

static int do_permaddr(struct cmd_context *ctx)
{
	unsigned int i;
	int err;
	struct ethtool_perm_addr *epaddr;

	epaddr = malloc(sizeof(struct ethtool_perm_addr) + MAX_ADDR_LEN);
	if (!epaddr) {
		perror("Cannot allocate memory for operation");
		return 1;
	}

	epaddr->cmd = ETHTOOL_GPERMADDR;
	epaddr->size = MAX_ADDR_LEN;

	err = send_ioctl(ctx, epaddr);
	if (err < 0)
		perror("Cannot read permanent address");
	else {
		printf("Permanent address:");
		for (i = 0; i < epaddr->size; i++)
			printf("%c%02x", (i == 0) ? ' ' : ':',
			       epaddr->data[i]);
		printf("\n");
	}
	free(epaddr);

	return err;
}

static bool flow_type_is_ntuple_supported(__u32 flow_type)
{
	switch (flow_type) {
	case TCP_V4_FLOW:
	case UDP_V4_FLOW:
	case SCTP_V4_FLOW:
	case AH_V4_FLOW:
	case ESP_V4_FLOW:
	case IPV4_USER_FLOW:
	case ETHER_FLOW:
		return true;
	default:
		return false;
	}
}

static int flow_spec_to_ntuple(struct ethtool_rx_flow_spec *fsp,
			       struct ethtool_rx_ntuple_flow_spec *ntuple)
{
	size_t i;

	/* verify location is not specified */
	if (fsp->location != RX_CLS_LOC_ANY)
		return -1;

	/* destination MAC address in L3/L4 rules is not supported by ntuple */
	if (fsp->flow_type & FLOW_MAC_EXT)
		return -1;

	/* verify ring cookie can transfer to action */
	if (fsp->ring_cookie > INT_MAX && fsp->ring_cookie < (u64)(-2))
		return -1;

	/* verify only one field is setting data field */
	if ((fsp->flow_type & FLOW_EXT) &&
	    (fsp->m_ext.data[0] || fsp->m_ext.data[1]) &&
	    fsp->m_ext.vlan_etype)
		return -1;

	/* IPv6 flow types are not supported by ntuple */
	if (!flow_type_is_ntuple_supported(fsp->flow_type & ~FLOW_EXT))
		return -1;

	/* Set entire ntuple to ~0 to guarantee all masks are set */
	memset(ntuple, ~0, sizeof(*ntuple));

	/* set non-filter values */
	ntuple->flow_type = fsp->flow_type;
	ntuple->action = fsp->ring_cookie;

	/*
	 * Copy over header union, they are identical in layout however
	 * the ntuple union contains additional padding on the end
	 */
	memcpy(&ntuple->h_u, &fsp->h_u, sizeof(fsp->h_u));

	/*
	 * The same rule mentioned above applies to the mask union.  However,
	 * in addition we need to invert the mask bits to match the ntuple
	 * mask which is 1 for masked, versus 0 for masked as seen in nfc.
	 */
	memcpy(&ntuple->m_u, &fsp->m_u, sizeof(fsp->m_u));
	for (i = 0; i < sizeof(fsp->m_u); i++)
		ntuple->m_u.hdata[i] ^= 0xFF;

	/* copy extended fields */
	if (fsp->flow_type & FLOW_EXT) {
		ntuple->vlan_tag =
			ntohs(fsp->h_ext.vlan_tci);
		ntuple->vlan_tag_mask =
			~ntohs(fsp->m_ext.vlan_tci);
		if (fsp->m_ext.vlan_etype) {
			/*
			 * vlan_etype and user data are mutually exclusive
			 * in ntuple configuration as they occupy the same
			 * space.
			 */
			if (fsp->m_ext.data[0] || fsp->m_ext.data[1])
				return -1;
			ntuple->data =
				ntohl(fsp->h_ext.vlan_etype);
			ntuple->data_mask =
				~(u64)ntohl(fsp->m_ext.vlan_etype);
		} else {
			ntuple->data =
				(u64)ntohl(fsp->h_ext.data[0]) << 32;
			ntuple->data |=
				(u64)ntohl(fsp->h_ext.data[1]);
			ntuple->data_mask =
				(u64)ntohl(~fsp->m_ext.data[0]) << 32;
			ntuple->data_mask |=
				(u64)ntohl(~fsp->m_ext.data[1]);
		}
	}

	/* Mask out the extended bit, because ntuple does not know it! */
	ntuple->flow_type &= ~FLOW_EXT;

	return 0;
}

static int do_srxntuple(struct cmd_context *ctx,
			struct ethtool_rx_flow_spec *rx_rule_fs)
{
	struct ethtool_rx_ntuple ntuplecmd;
	struct ethtool_value eval;
	int err;

	/* attempt to convert the flow classifier to an ntuple classifier */
	err = flow_spec_to_ntuple(rx_rule_fs, &ntuplecmd.fs);
	if (err)
		return -1;

	/*
	 * Check to see if the flag is set for N-tuple, this allows
	 * us to avoid the possible EINVAL response for the N-tuple
	 * flag not being set on the device
	 */
	eval.cmd = ETHTOOL_GFLAGS;
	err = send_ioctl(ctx, &eval);
	if (err || !(eval.data & ETH_FLAG_NTUPLE))
		return -1;

	/* send rule via N-tuple */
	ntuplecmd.cmd = ETHTOOL_SRXNTUPLE;
	err = send_ioctl(ctx, &ntuplecmd);

	/*
	 * Display error only if response is something other than op not
	 * supported.  It is possible that the interface uses the network
	 * flow classifier interface instead of N-tuple.
	 */
	if (err < 0) {
		if (errno != EOPNOTSUPP)
			perror("Cannot add new rule via N-tuple");
		return -1;
	}

	return 0;
}

static int do_writefwdump(struct ethtool_dump *dump, const char *dump_file)
{
	int err = 0;
	FILE *f;
	size_t bytes;

	f = fopen(dump_file, "wb+");

	if (!f) {
		fprintf(stderr, "Can't open file %s: %s\n",
			dump_file, strerror(errno));
		return 1;
	}
	bytes = fwrite(dump->data, 1, dump->len, f);
	if (bytes != dump->len) {
		fprintf(stderr, "Can not write all of dump data\n");
		err = 1;
	}
	if (fclose(f)) {
		fprintf(stderr, "Can't close file %s: %s\n",
			dump_file, strerror(errno));
		err = 1;
	}
	return err;
}

static int do_getfwdump(struct cmd_context *ctx)
{
	u32 dump_flag;
	char *dump_file;
	int err;
	struct ethtool_dump edata;
	struct ethtool_dump *data;

	if (ctx->argc == 2 && !strcmp(ctx->argp[0], "data")) {
		dump_flag = ETHTOOL_GET_DUMP_DATA;
		dump_file = ctx->argp[1];
	} else if (ctx->argc == 0) {
		dump_flag = 0;
		dump_file = NULL;
	} else {
		exit_bad_args();
	}

	edata.cmd = ETHTOOL_GET_DUMP_FLAG;

	err = send_ioctl(ctx, &edata);
	if (err < 0) {
		perror("Can not get dump level");
		return 1;
	}
	if (dump_flag != ETHTOOL_GET_DUMP_DATA) {
		fprintf(stdout, "flag: %u, version: %u, length: %u\n",
			edata.flag, edata.version, edata.len);
		return 0;
	}
	data = calloc(1, offsetof(struct ethtool_dump, data) + edata.len);
	if (!data) {
		perror("Can not allocate enough memory");
		return 1;
	}
	data->cmd = ETHTOOL_GET_DUMP_DATA;
	data->len = edata.len;
	err = send_ioctl(ctx, data);
	if (err < 0) {
		perror("Can not get dump data");
		err = 1;
		goto free;
	}
	err = do_writefwdump(data, dump_file);
free:
	free(data);
	return err;
}

static int do_setfwdump(struct cmd_context *ctx)
{
	u32 dump_flag;
	int err;
	struct ethtool_dump dump;

	if (ctx->argc != 1)
		exit_bad_args();
	dump_flag = get_u32(ctx->argp[0], 0);

	dump.cmd = ETHTOOL_SET_DUMP;
	dump.flag = dump_flag;
	err = send_ioctl(ctx, &dump);
	if (err < 0) {
		perror("Can not set dump level");
		return 1;
	}
	return 0;
}

static int do_gprivflags(struct cmd_context *ctx)
{
	struct ethtool_gstrings *strings;
	struct ethtool_value flags;
	unsigned int i;
	int max_len = 0, cur_len, rc;

	if (ctx->argc != 0)
		exit_bad_args();

	strings = get_stringset(ctx, ETH_SS_PRIV_FLAGS,
				offsetof(struct ethtool_drvinfo, n_priv_flags),
				1);
	if (!strings) {
		perror("Cannot get private flag names");
		return 1;
	}
	if (strings->len == 0) {
		fprintf(stderr, "No private flags defined\n");
		rc = 1;
		goto err;
	}
	if (strings->len > 32) {
		/* ETHTOOL_GPFLAGS can only cover 32 flags */
		fprintf(stderr, "Only showing first 32 private flags\n");
		strings->len = 32;
	}

	flags.cmd = ETHTOOL_GPFLAGS;
	if (send_ioctl(ctx, &flags)) {
		perror("Cannot get private flags");
		rc = 1;
		goto err;
	}

	/* Find longest string and align all strings accordingly */
	for (i = 0; i < strings->len; i++) {
		cur_len = strlen((const char *)strings->data +
				 i * ETH_GSTRING_LEN);
		if (cur_len > max_len)
			max_len = cur_len;
	}

	printf("Private flags for %s:\n", ctx->devname);
	for (i = 0; i < strings->len; i++)
		printf("%-*s: %s\n",
		       max_len,
		       (const char *)strings->data + i * ETH_GSTRING_LEN,
		       (flags.data & (1U << i)) ? "on" : "off");

	rc = 0;

err:
	free(strings);
	return rc;
}

static int do_sprivflags(struct cmd_context *ctx)
{
	struct ethtool_gstrings *strings;
	struct cmdline_info *cmdline;
	struct ethtool_value flags;
	u32 wanted_flags = 0, seen_flags = 0;
	int any_changed, rc;
	unsigned int i;

	strings = get_stringset(ctx, ETH_SS_PRIV_FLAGS,
				offsetof(struct ethtool_drvinfo, n_priv_flags),
				1);
	if (!strings) {
		perror("Cannot get private flag names");
		return 1;
	}
	if (strings->len == 0) {
		fprintf(stderr, "No private flags defined\n");
		rc = 1;
		goto err;
	}
	if (strings->len > 32) {
		/* ETHTOOL_{G,S}PFLAGS can only cover 32 flags */
		fprintf(stderr, "Only setting first 32 private flags\n");
		strings->len = 32;
	}

	cmdline = calloc(strings->len, sizeof(*cmdline));
	if (!cmdline) {
		perror("Cannot parse arguments");
		rc = 1;
		goto err;
	}
	for (i = 0; i < strings->len; i++) {
		cmdline[i].name = ((const char *)strings->data +
				   i * ETH_GSTRING_LEN);
		cmdline[i].type = CMDL_FLAG;
		cmdline[i].wanted_val = &wanted_flags;
		cmdline[i].flag_val = 1U << i;
		cmdline[i].seen_val = &seen_flags;
	}
	parse_generic_cmdline(ctx, &any_changed, cmdline, strings->len);
	free(cmdline);

	flags.cmd = ETHTOOL_GPFLAGS;
	if (send_ioctl(ctx, &flags)) {
		perror("Cannot get private flags");
		rc = 1;
		goto err;
	}

	flags.cmd = ETHTOOL_SPFLAGS;
	flags.data = (flags.data & ~seen_flags) | wanted_flags;
	if (send_ioctl(ctx, &flags)) {
		perror("Cannot set private flags");
		rc = 1;
		goto err;
	}

	rc = 0;
err:
	free(strings);
	return rc;
}

static int do_tsinfo(struct cmd_context *ctx)
{
	struct ethtool_ts_info info;

	if (ctx->argc != 0)
		exit_bad_args();

	fprintf(stdout, "Time stamping parameters for %s:\n", ctx->devname);
	info.cmd = ETHTOOL_GET_TS_INFO;
	if (send_ioctl(ctx, &info)) {
		perror("Cannot get device time stamping settings");
		return -1;
	}
	dump_tsinfo(&info);
	return 0;
}

static int do_getmodule(struct cmd_context *ctx)
{
	struct ethtool_modinfo modinfo;
	struct ethtool_eeprom *eeprom;
	u32 geeprom_offset = 0;
	u32 geeprom_length = 0;
	int geeprom_changed = 0;
	int geeprom_dump_raw = 0;
	int geeprom_dump_hex = 0;
	int geeprom_length_seen = 0;
	int err;

	struct cmdline_info cmdline_geeprom[] = {
		{
			.name		= "offset",
			.type		= CMDL_U32,
			.wanted_val	= &geeprom_offset,
		},
		{
			.name		= "length",
			.type		= CMDL_U32,
			.wanted_val	= &geeprom_length,
			.seen_val	= &geeprom_length_seen,
		},
		{
			.name		= "raw",
			.type		= CMDL_BOOL,
			.wanted_val	= &geeprom_dump_raw,
		},
		{
			.name		= "hex",
			.type		= CMDL_BOOL,
			.wanted_val	= &geeprom_dump_hex,
		},
	};

	parse_generic_cmdline(ctx, &geeprom_changed,
			      cmdline_geeprom, ARRAY_SIZE(cmdline_geeprom));

	if (geeprom_dump_raw && geeprom_dump_hex) {
		printf("Hex and raw dump cannot be specified together\n");
		return 1;
	}

	modinfo.cmd = ETHTOOL_GMODULEINFO;
	err = send_ioctl(ctx, &modinfo);
	if (err < 0) {
		perror("Cannot get module EEPROM information");
		return 1;
	}

	if (!geeprom_length_seen)
		geeprom_length = modinfo.eeprom_len;

	if (modinfo.eeprom_len < geeprom_offset + geeprom_length)
		geeprom_length = modinfo.eeprom_len - geeprom_offset;

	eeprom = calloc(1, sizeof(*eeprom)+geeprom_length);
	if (!eeprom) {
		perror("Cannot allocate memory for Module EEPROM data");
		return 1;
	}

	eeprom->cmd = ETHTOOL_GMODULEEEPROM;
	eeprom->len = geeprom_length;
	eeprom->offset = geeprom_offset;
	err = send_ioctl(ctx, eeprom);
	if (err < 0) {
		int saved_errno = errno;

		perror("Cannot get Module EEPROM data");
		if (saved_errno == ENODEV || saved_errno == EIO ||
		    saved_errno == ENXIO)
			fprintf(stderr, "SFP module not in cage?\n");
		free(eeprom);
		return 1;
	}

	/*
	 * SFF-8079 EEPROM layout contains the memory available at A0 address on
	 * the PHY EEPROM.
	 * SFF-8472 defines a virtual extension of the EEPROM, where the
	 * microcontroller on the SFP/SFP+ generates a page at the A2 address,
	 * which contains data relative to optical diagnostics.
	 * The current kernel implementation returns a blob, which contains:
	 *  - ETH_MODULE_SFF_8079 => The A0 page only.
	 *  - ETH_MODULE_SFF_8472 => The A0 and A2 page concatenated.
	 */
	if (geeprom_dump_raw) {
		fwrite(eeprom->data, 1, eeprom->len, stdout);
	} else {
		if (eeprom->offset != 0  ||
		    (eeprom->len != modinfo.eeprom_len)) {
			geeprom_dump_hex = 1;
		} else if (!geeprom_dump_hex) {
			switch (modinfo.type) {
#ifdef ETHTOOL_ENABLE_PRETTY_DUMP
			case ETH_MODULE_SFF_8079:
				sff8079_show_all_ioctl(eeprom->data);
				break;
			case ETH_MODULE_SFF_8472:
				sff8079_show_all_ioctl(eeprom->data);
				sff8472_show_all(eeprom->data);
				break;
			case ETH_MODULE_SFF_8436:
			case ETH_MODULE_SFF_8636:
				sff8636_show_all_ioctl(eeprom->data,
						       modinfo.eeprom_len);
				break;
#endif
			default:
				geeprom_dump_hex = 1;
				break;
			}
		}
		if (geeprom_dump_hex)
			dump_hex(stdout, eeprom->data,
				 eeprom->len, eeprom->offset);
	}

	free(eeprom);

	return 0;
}

static int do_geee(struct cmd_context *ctx)
{
	struct ethtool_eee eeecmd;

	if (ctx->argc != 0)
		exit_bad_args();

	eeecmd.cmd = ETHTOOL_GEEE;
	if (send_ioctl(ctx, &eeecmd)) {
		perror("Cannot get EEE settings");
		return 1;
	}

	fprintf(stdout, "EEE Settings for %s:\n", ctx->devname);
	dump_eeecmd(&eeecmd);

	return 0;
}

static int do_seee(struct cmd_context *ctx)
{
	int adv_c = -1, lpi_c = -1, lpi_time_c = -1, eee_c = -1;
	int change = -1, change2 = 0;
	struct ethtool_eee eeecmd;
	struct cmdline_info cmdline_eee[] = {
		{
			.name		= "advertise",
			.type		= CMDL_U32,
			.wanted_val	= &adv_c,
			.ioctl_val	= &eeecmd.advertised,
		},
		{
			.name		= "tx-lpi",
			.type		= CMDL_BOOL,
			.wanted_val	= &lpi_c,
			.ioctl_val	= &eeecmd.tx_lpi_enabled,
		},
		{
			.name		= "tx-timer",
			.type		= CMDL_U32,
			.wanted_val	= &lpi_time_c,
			.ioctl_val	= &eeecmd.tx_lpi_timer,
		},
		{
			.name		= "eee",
			.type		= CMDL_BOOL,
			.wanted_val	= &eee_c,
			.ioctl_val	= &eeecmd.eee_enabled,
		},
	};

	if (ctx->argc == 0)
		exit_bad_args();

	parse_generic_cmdline(ctx, &change, cmdline_eee,
			      ARRAY_SIZE(cmdline_eee));

	eeecmd.cmd = ETHTOOL_GEEE;
	if (send_ioctl(ctx, &eeecmd)) {
		perror("Cannot get EEE settings");
		return 1;
	}

	do_generic_set(cmdline_eee, ARRAY_SIZE(cmdline_eee), &change2);

	if (change2) {
		eeecmd.cmd = ETHTOOL_SEEE;
		if (send_ioctl(ctx, &eeecmd)) {
			perror("Cannot set EEE settings");
			return 1;
		}
	}

	return 0;
}

/* copy of net/ethtool/common.c */
char
tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
	[ETHTOOL_ID_UNSPEC]		= "Unspec",
	[ETHTOOL_RX_COPYBREAK]		= "rx-copybreak",
	[ETHTOOL_TX_COPYBREAK]		= "tx-copybreak",
	[ETHTOOL_TX_COPYBREAK_BUF_SIZE] = "tx-buf-size",
	[ETHTOOL_PFC_PREVENTION_TOUT]	= "pfc-prevention-tout",
};

union ethtool_tunable_info_val {
	uint8_t u8;
	uint16_t u16;
	uint32_t u32;
	uint64_t u64;
	int8_t s8;
	int16_t s16;
	int32_t s32;
	int64_t s64;
};

struct ethtool_tunable_info {
	enum tunable_id t_id;
	enum tunable_type_id t_type_id;
	size_t size;
	cmdline_type_t type;
	union ethtool_tunable_info_val wanted;
	int seen;
};

static struct ethtool_tunable_info tunables_info[] = {
	{ .t_id		= ETHTOOL_RX_COPYBREAK,
	  .t_type_id	= ETHTOOL_TUNABLE_U32,
	  .size		= sizeof(u32),
	  .type		= CMDL_U32,
	},
	{ .t_id		= ETHTOOL_TX_COPYBREAK,
	  .t_type_id	= ETHTOOL_TUNABLE_U32,
	  .size		= sizeof(u32),
	  .type		= CMDL_U32,
	},
	{ .t_id		= ETHTOOL_PFC_PREVENTION_TOUT,
	  .t_type_id	= ETHTOOL_TUNABLE_U16,
	  .size		= sizeof(u16),
	  .type		= CMDL_U16,
	},
	{ .t_id         = ETHTOOL_TX_COPYBREAK_BUF_SIZE,
	  .t_type_id    = ETHTOOL_TUNABLE_U32,
	  .size         = sizeof(u32),
	  .type         = CMDL_U32,
	},
};
#define TUNABLES_INFO_SIZE	ARRAY_SIZE(tunables_info)

static int do_stunable(struct cmd_context *ctx)
{
	struct cmdline_info cmdline_tunable[TUNABLES_INFO_SIZE];
	struct ethtool_tunable_info *tinfo = tunables_info;
	int changed = 0;
	unsigned int i;

	for (i = 0; i < TUNABLES_INFO_SIZE; i++) {
		cmdline_tunable[i].name = tunable_strings[tinfo[i].t_id];
		cmdline_tunable[i].type = tinfo[i].type;
		cmdline_tunable[i].wanted_val = &tinfo[i].wanted;
		cmdline_tunable[i].seen_val = &tinfo[i].seen;
	}

	parse_generic_cmdline(ctx, &changed, cmdline_tunable, TUNABLES_INFO_SIZE);
	if (!changed)
		exit_bad_args();

	for (i = 0; i < TUNABLES_INFO_SIZE; i++) {
		struct ethtool_tunable *tuna;
		size_t size;
		int ret;

		if (!tinfo[i].seen)
			continue;

		size = sizeof(*tuna) + tinfo[i].size;
		tuna = calloc(1, size);
		if (!tuna) {
			perror(tunable_strings[tinfo[i].t_id]);
			return 1;
		}
		tuna->cmd = ETHTOOL_STUNABLE;
		tuna->id = tinfo[i].t_id;
		tuna->type_id = tinfo[i].t_type_id;
		tuna->len = tinfo[i].size;
		memcpy(tuna->data, &tinfo[i].wanted, tuna->len);
		ret = send_ioctl(ctx, tuna);
		if (ret) {
			perror(tunable_strings[tuna->id]);
			free(tuna);
			return ret;
		}
		free(tuna);
	}
	return 0;
}

static void print_tunable(struct ethtool_tunable *tuna)
{
	char *name = tunable_strings[tuna->id];
	union ethtool_tunable_info_val *val;

	val = (union ethtool_tunable_info_val *)tuna->data;
	switch (tuna->type_id) {
	case ETHTOOL_TUNABLE_U8:
		fprintf(stdout, "%s: %" PRIu8 "\n", name, val->u8);
		break;
	case ETHTOOL_TUNABLE_U16:
		fprintf(stdout, "%s: %" PRIu16 "\n", name, val->u16);
		break;
	case ETHTOOL_TUNABLE_U32:
		fprintf(stdout, "%s: %" PRIu32 "\n", name, val->u32);
		break;
	case ETHTOOL_TUNABLE_U64:
		fprintf(stdout, "%s: %" PRIu64 "\n", name, val->u64);
		break;
	case ETHTOOL_TUNABLE_S8:
		fprintf(stdout, "%s: %" PRId8 "\n", name, val->s8);
		break;
	case ETHTOOL_TUNABLE_S16:
		fprintf(stdout, "%s: %" PRId16 "\n", name, val->s16);
		break;
	case ETHTOOL_TUNABLE_S32:
		fprintf(stdout, "%s: %" PRId32 "\n", name, val->s32);
		break;
	case ETHTOOL_TUNABLE_S64:
		fprintf(stdout, "%s: %" PRId64 "\n", name, val->s64);
		break;
	default:
		fprintf(stdout, "%s: Unknown format\n", name);
	}
}

static int do_gtunable(struct cmd_context *ctx)
{
	struct ethtool_tunable_info *tinfo = tunables_info;
	char **argp = ctx->argp;
	unsigned int argc = ctx->argc;
	unsigned int i, j;

	if (argc < 1)
		exit_bad_args();

	for (i = 0; i < argc; i++) {
		int valid = 0;

		for (j = 0; j < TUNABLES_INFO_SIZE; j++) {
			char *ts = tunable_strings[tinfo[j].t_id];
			struct ethtool_tunable *tuna;
			int ret;

			if (strcmp(argp[i], ts))
				continue;
			valid = 1;

			tuna = calloc(1, sizeof(*tuna) + tinfo[j].size);
			if (!tuna) {
				perror(ts);
				return 1;
			}
			tuna->cmd = ETHTOOL_GTUNABLE;
			tuna->id = tinfo[j].t_id;
			tuna->type_id = tinfo[j].t_type_id;
			tuna->len = tinfo[j].size;
			ret = send_ioctl(ctx, tuna);
			if (ret) {
				fprintf(stderr, "%s: Cannot get tunable\n", ts);
				free(tuna);
				return ret;
			}
			print_tunable(tuna);
			free(tuna);
		}
		if (!valid)
			exit_bad_args();
	}
	return 0;
}

static int do_get_phy_tunable(struct cmd_context *ctx)
{
	unsigned int argc = ctx->argc;
	char **argp = ctx->argp;

	if (argc < 1)
		exit_bad_args();

	if (!strcmp(argp[0], "downshift")) {
		struct {
			struct ethtool_tunable ds;
			u8 count;
		} cont;

		cont.ds.cmd = ETHTOOL_PHY_GTUNABLE;
		cont.ds.id = ETHTOOL_PHY_DOWNSHIFT;
		cont.ds.type_id = ETHTOOL_TUNABLE_U8;
		cont.ds.len = 1;
		if (send_ioctl(ctx, &cont.ds) < 0) {
			perror("Cannot Get PHY downshift count");
			return 87;
		}
		if (cont.count)
			fprintf(stdout, "Downshift count: %d\n", cont.count);
		else
			fprintf(stdout, "Downshift disabled\n");
	} else if (!strcmp(argp[0], "fast-link-down")) {
		struct {
			struct ethtool_tunable fld;
			u8 msecs;
		} cont;

		cont.fld.cmd = ETHTOOL_PHY_GTUNABLE;
		cont.fld.id = ETHTOOL_PHY_FAST_LINK_DOWN;
		cont.fld.type_id = ETHTOOL_TUNABLE_U8;
		cont.fld.len = 1;
		if (send_ioctl(ctx, &cont.fld) < 0) {
			perror("Cannot Get PHY Fast Link Down value");
			return 87;
		}

		if (cont.msecs == ETHTOOL_PHY_FAST_LINK_DOWN_ON)
			fprintf(stdout, "Fast Link Down enabled\n");
		else if (cont.msecs == ETHTOOL_PHY_FAST_LINK_DOWN_OFF)
			fprintf(stdout, "Fast Link Down disabled\n");
		else
			fprintf(stdout, "Fast Link Down enabled, %d msecs\n",
				cont.msecs);
	} else if (!strcmp(argp[0], "energy-detect-power-down")) {
		struct {
			struct ethtool_tunable ds;
			u16 msecs;
		} cont;

		cont.ds.cmd = ETHTOOL_PHY_GTUNABLE;
		cont.ds.id = ETHTOOL_PHY_EDPD;
		cont.ds.type_id = ETHTOOL_TUNABLE_U16;
		cont.ds.len = 2;
		if (send_ioctl(ctx, &cont.ds) < 0) {
			perror("Cannot Get PHY Energy Detect Power Down value");
			return 87;
		}

		if (cont.msecs == ETHTOOL_PHY_EDPD_DISABLE)
			fprintf(stdout, "Energy Detect Power Down: disabled\n");
		else if (cont.msecs == ETHTOOL_PHY_EDPD_NO_TX)
			fprintf(stdout,
				"Energy Detect Power Down: enabled, TX disabled\n");
		else
			fprintf(stdout,
				"Energy Detect Power Down: enabled, TX %u msecs\n",
				cont.msecs);
	} else {
		exit_bad_args();
	}

	return 0;
}

static __u32 parse_reset(char *val, __u32 bitset, char *arg, __u32 *data)
{
	__u32 bitval = 0;
	int i;

	/* Check for component match */
	for (i = 0; val[i] != '\0'; i++)
		if (arg[i] != val[i])
			return 0;

	/* Check if component has -shared specified or not */
	if (arg[i] == '\0')
		bitval = bitset;
	else if (!strcmp(arg+i, "-shared"))
		bitval = bitset << ETH_RESET_SHARED_SHIFT;

	if (bitval) {
		*data |= bitval;
		return 1;
	}
	return 0;
}

static int do_reset(struct cmd_context *ctx)
{
	struct ethtool_value resetinfo;
	__u32 data;
	unsigned int argc = ctx->argc;
	char **argp = ctx->argp;
	unsigned int i;

	if (argc == 0)
		exit_bad_args();

	data = 0;

	for (i = 0; i < argc; i++) {
		if (!strcmp(argp[i], "flags")) {
			__u32 flags;

			i++;
			if (i >= argc)
				exit_bad_args();
			flags = strtoul(argp[i], NULL, 0);
			if (flags == 0)
				exit_bad_args();
			else
				data |= flags;
		} else if (parse_reset("mgmt", ETH_RESET_MGMT,
				      argp[i], &data)) {
		} else if (parse_reset("irq",  ETH_RESET_IRQ,
				    argp[i], &data)) {
		} else if (parse_reset("dma", ETH_RESET_DMA,
				    argp[i], &data)) {
		} else if (parse_reset("filter", ETH_RESET_FILTER,
				    argp[i], &data)) {
		} else if (parse_reset("offload", ETH_RESET_OFFLOAD,
				    argp[i], &data)) {
		} else if (parse_reset("mac", ETH_RESET_MAC,
				    argp[i], &data)) {
		} else if (parse_reset("phy", ETH_RESET_PHY,
				    argp[i], &data)) {
		} else if (parse_reset("ram", ETH_RESET_RAM,
				    argp[i], &data)) {
		} else if (parse_reset("ap", ETH_RESET_AP,
				    argp[i], &data)) {
		} else if (!strcmp(argp[i], "dedicated")) {
			data |= ETH_RESET_DEDICATED;
		} else if (!strcmp(argp[i], "all")) {
			data |= ETH_RESET_ALL;
		} else {
			exit_bad_args();
		}
	}

	resetinfo.cmd = ETHTOOL_RESET;
	resetinfo.data = data;
	fprintf(stdout, "ETHTOOL_RESET 0x%x\n", resetinfo.data);

	if (send_ioctl(ctx, &resetinfo)) {
		perror("Cannot issue ETHTOOL_RESET");
		return 1;
	}

	fprintf(stdout, "Components reset:     0x%x\n", data & ~resetinfo.data);
	if (resetinfo.data)
		fprintf(stdout, "Components not reset: 0x%x\n", resetinfo.data);

	return 0;
}

static int parse_named_bool(struct cmd_context *ctx, const char *name, u8 *on)
{
	if (ctx->argc < 2)
		return 0;

	if (strcmp(*ctx->argp, name))
		return 0;

	if (!strcmp(*(ctx->argp + 1), "on")) {
		*on = 1;
	} else if (!strcmp(*(ctx->argp + 1), "off")) {
		*on = 0;
	} else {
		fprintf(stderr, "Invalid boolean\n");
		exit_bad_args();
	}

	ctx->argc -= 2;
	ctx->argp += 2;

	return 1;
}

static int parse_named_uint(struct cmd_context *ctx,
			    const char *name,
			    unsigned long long *val,
			    unsigned long long max)
{
	if (ctx->argc < 2)
		return 0;

	if (strcmp(*ctx->argp, name))
		return 0;

	*val = get_uint_range(*(ctx->argp + 1), 0, max);

	ctx->argc -= 2;
	ctx->argp += 2;

	return 1;
}

static int parse_named_u8(struct cmd_context *ctx, const char *name, u8 *val)
{
	unsigned long long val1;
	int ret;

	ret = parse_named_uint(ctx, name, &val1, 0xff);
	if (ret)
		*val = val1;

	return ret;
}

static int parse_named_u16(struct cmd_context *ctx, const char *name, u16 *val)
{
	unsigned long long val1;
	int ret;

	ret = parse_named_uint(ctx, name, &val1, 0xffff);
	if (ret)
		*val = val1;

	return ret;
}

static int do_set_phy_tunable(struct cmd_context *ctx)
{
	int err = 0;
	u8 ds_cnt = DOWNSHIFT_DEV_DEFAULT_COUNT;
	u8 ds_changed = 0, ds_has_cnt = 0, ds_enable = 0;
	u8 fld_changed = 0, fld_enable = 0;
	u8 fld_msecs = ETHTOOL_PHY_FAST_LINK_DOWN_ON;
	u8 edpd_changed = 0, edpd_enable = 0;
	u16 edpd_tx_interval = ETHTOOL_PHY_EDPD_DFLT_TX_MSECS;

	/* Parse arguments */
	if (parse_named_bool(ctx, "downshift", &ds_enable)) {
		ds_changed = 1;
		ds_has_cnt = parse_named_u8(ctx, "count", &ds_cnt);
	} else if (parse_named_bool(ctx, "fast-link-down", &fld_enable)) {
		fld_changed = 1;
		if (fld_enable)
			parse_named_u8(ctx, "msecs", &fld_msecs);
	} else if (parse_named_bool(ctx, "energy-detect-power-down",
				    &edpd_enable)) {
		edpd_changed = 1;
		if (edpd_enable)
			parse_named_u16(ctx, "msecs", &edpd_tx_interval);
	} else {
		exit_bad_args();
	}

	/* Validate parameters */
	if (ds_changed) {
		if (!ds_enable && ds_has_cnt) {
			fprintf(stderr, "'count' may not be set when downshift "
				        "is off.\n");
			exit_bad_args();
		}

		if (ds_enable && ds_has_cnt && ds_cnt == 0) {
			fprintf(stderr, "'count' may not be zero.\n");
			exit_bad_args();
		}

		if (!ds_enable)
			ds_cnt = DOWNSHIFT_DEV_DISABLE;
	} else if (fld_changed) {
		if (!fld_enable)
			fld_msecs = ETHTOOL_PHY_FAST_LINK_DOWN_OFF;
		else if (fld_msecs == ETHTOOL_PHY_FAST_LINK_DOWN_OFF)
			exit_bad_args();
	} else if (edpd_changed) {
		if (!edpd_enable)
			edpd_tx_interval = ETHTOOL_PHY_EDPD_DISABLE;
		else if (edpd_tx_interval == 0)
			edpd_tx_interval = ETHTOOL_PHY_EDPD_NO_TX;
		else if (edpd_tx_interval > ETHTOOL_PHY_EDPD_NO_TX) {
			fprintf(stderr, "'msecs' max value is %d.\n",
				(ETHTOOL_PHY_EDPD_NO_TX - 1));
			exit_bad_args();
		}
	}

	/* Do it */
	if (ds_changed) {
		struct {
			struct ethtool_tunable ds;
			u8 count;
		} cont;

		cont.ds.cmd = ETHTOOL_PHY_STUNABLE;
		cont.ds.id = ETHTOOL_PHY_DOWNSHIFT;
		cont.ds.type_id = ETHTOOL_TUNABLE_U8;
		cont.ds.len = 1;
		cont.count = ds_cnt;
		err = send_ioctl(ctx, &cont.ds);
		if (err < 0) {
			perror("Cannot Set PHY downshift count");
			err = 87;
		}
	} else if (fld_changed) {
		struct {
			struct ethtool_tunable fld;
			u8 msecs;
		} cont;

		cont.fld.cmd = ETHTOOL_PHY_STUNABLE;
		cont.fld.id = ETHTOOL_PHY_FAST_LINK_DOWN;
		cont.fld.type_id = ETHTOOL_TUNABLE_U8;
		cont.fld.len = 1;
		cont.msecs = fld_msecs;
		err = send_ioctl(ctx, &cont.fld);
		if (err < 0) {
			perror("Cannot Set PHY Fast Link Down value");
			err = 87;
		}
	} else if (edpd_changed) {
		struct {
			struct ethtool_tunable fld;
			u16 msecs;
		} cont;

		cont.fld.cmd = ETHTOOL_PHY_STUNABLE;
		cont.fld.id = ETHTOOL_PHY_EDPD;
		cont.fld.type_id = ETHTOOL_TUNABLE_U16;
		cont.fld.len = 2;
		cont.msecs = edpd_tx_interval;
		err = send_ioctl(ctx, &cont.fld);
		if (err < 0) {
			perror("Cannot Set PHY Energy Detect Power Down");
			err = 87;
		}
	}

	return err;
}

static int fecmode_str_to_type(const char *str)
{
	if (!strcasecmp(str, "auto"))
		return ETHTOOL_FEC_AUTO;
	if (!strcasecmp(str, "off"))
		return ETHTOOL_FEC_OFF;
	if (!strcasecmp(str, "rs"))
		return ETHTOOL_FEC_RS;
	if (!strcasecmp(str, "baser"))
		return ETHTOOL_FEC_BASER;
	if (!strcasecmp(str, "llrs"))
		return ETHTOOL_FEC_LLRS;
	return 0;
}

static int do_gfec(struct cmd_context *ctx)
{
	struct ethtool_fecparam feccmd = { 0 };
	int rv;

	if (ctx->argc != 0)
		exit_bad_args();

	feccmd.cmd = ETHTOOL_GFECPARAM;
	rv = send_ioctl(ctx, &feccmd);
	if (rv != 0) {
		perror("Cannot get FEC settings");
		return rv;
	}

	fprintf(stdout, "FEC parameters for %s:\n", ctx->devname);
	fprintf(stdout, "Supported/Configured FEC encodings:");
	dump_fec(feccmd.fec);
	fprintf(stdout, "\n");

	fprintf(stdout, "Active FEC encoding:");
	dump_fec(feccmd.active_fec);
	fprintf(stdout, "\n");

	return 0;
}

static int do_sfec(struct cmd_context *ctx)
{
	enum { ARG_NONE, ARG_ENCODING } state = ARG_NONE;
	struct ethtool_fecparam feccmd;
	int fecmode = 0, newmode;
	unsigned int i;
	int rv;

	for (i = 0; i < ctx->argc; i++) {
		if (!strcmp(ctx->argp[i], "encoding")) {
			state = ARG_ENCODING;
			continue;
		}
		if (state == ARG_ENCODING) {
			newmode = fecmode_str_to_type(ctx->argp[i]);
			if (!newmode)
				exit_bad_args();
			fecmode |= newmode;
			continue;
		}
		exit_bad_args();
	}

	if (!fecmode)
		exit_bad_args();

	feccmd.cmd = ETHTOOL_SFECPARAM;
	feccmd.fec = fecmode;
	rv = send_ioctl(ctx, &feccmd);
	if (rv != 0) {
		perror("Cannot set FEC settings");
		return rv;
	}

	return 0;
}

static int do_perqueue(struct cmd_context *ctx);

#ifndef TEST_ETHTOOL
int send_ioctl(struct cmd_context *ctx, void *cmd)
{
	ctx->ifr.ifr_data = cmd;
	return ioctl(ctx->fd, SIOCETHTOOL, &ctx->ifr);
}
#endif

static int show_usage(struct cmd_context *ctx);

struct option {
	const char	*opts;
	bool		no_dev;
	bool		json;
	int		(*func)(struct cmd_context *);
	nl_chk_t	nlchk;
	nl_func_t	nlfunc;
	const char	*help;
	const char	*xhelp;
};

static const struct option args[] = {
	{
		/* "default" entry when no switch is used */
		.opts	= "",
		.func	= do_gset,
		.nlfunc	= nl_gset,
		.help	= "Display standard information about device",
	},
	{
		.opts	= "-s|--change",
		.func	= do_sset,
		.nlfunc	= nl_sset,
		.help	= "Change generic options",
		.xhelp	= "		[ speed %d ]\n"
			  "		[ lanes %d ]\n"
			  "		[ duplex half|full ]\n"
			  "		[ port tp|aui|bnc|mii|fibre|da ]\n"
			  "		[ mdix auto|on|off ]\n"
			  "		[ autoneg on|off ]\n"
			  "		[ advertise %x[/%x] | mode on|off ... [--] ]\n"
			  "		[ phyad %d ]\n"
			  "		[ xcvr internal|external ]\n"
			  "		[ wol %d[/%d] | p|u|m|b|a|g|s|f|d... ]\n"
			  "		[ sopass %x:%x:%x:%x:%x:%x ]\n"
			  "		[ msglvl %d[/%d] | type on|off ... [--] ]\n"
			  "		[ master-slave preferred-master|preferred-slave|forced-master|forced-slave ]\n"
	},
	{
		.opts	= "-a|--show-pause",
		.json	= true,
		.func	= do_gpause,
		.nlfunc	= nl_gpause,
		.help	= "Show pause options",
		.xhelp	= "		[ --src aggregate | emac | pmac ]\n"
	},
	{
		.opts	= "-A|--pause",
		.func	= do_spause,
		.nlfunc	= nl_spause,
		.help	= "Set pause options",
		.xhelp	= "		[ autoneg on|off ]\n"
			  "		[ rx on|off ]\n"
			  "		[ tx on|off ]\n"
	},
	{
		.opts	= "-c|--show-coalesce",
		.json	= true,
		.func	= do_gcoalesce,
		.nlfunc	= nl_gcoalesce,
		.help	= "Show coalesce options"
	},
	{
		.opts	= "-C|--coalesce",
		.func	= do_scoalesce,
		.nlfunc	= nl_scoalesce,
		.help	= "Set coalesce options",
		.xhelp	= "		[adaptive-rx on|off]\n"
			  "		[adaptive-tx on|off]\n"
			  "		[rx-usecs N]\n"
			  "		[rx-frames N]\n"
			  "		[rx-usecs-irq N]\n"
			  "		[rx-frames-irq N]\n"
			  "		[tx-usecs N]\n"
			  "		[tx-frames N]\n"
			  "		[tx-usecs-irq N]\n"
			  "		[tx-frames-irq N]\n"
			  "		[stats-block-usecs N]\n"
			  "		[pkt-rate-low N]\n"
			  "		[rx-usecs-low N]\n"
			  "		[rx-frames-low N]\n"
			  "		[tx-usecs-low N]\n"
			  "		[tx-frames-low N]\n"
			  "		[pkt-rate-high N]\n"
			  "		[rx-usecs-high N]\n"
			  "		[rx-frames-high N]\n"
			  "		[tx-usecs-high N]\n"
			  "		[tx-frames-high N]\n"
			  "		[sample-interval N]\n"
			  "		[cqe-mode-rx on|off]\n"
			  "		[cqe-mode-tx on|off]\n"
			  "		[tx-aggr-max-bytes N]\n"
			  "		[tx-aggr-max-frames N]\n"
			  "		[tx-aggr-time-usecs N]\n"
	},
	{
		.opts	= "-g|--show-ring",
		.json	= true,
		.func	= do_gring,
		.nlfunc	= nl_gring,
		.help	= "Query RX/TX ring parameters"
	},
	{
		.opts	= "-G|--set-ring",
		.func	= do_sring,
		.nlfunc	= nl_sring,
		.help	= "Set RX/TX ring parameters",
		.xhelp	= "		[ rx N ]\n"
			  "		[ rx-mini N ]\n"
			  "		[ rx-jumbo N ]\n"
			  "		[ tx N ]\n"
			  "		[ rx-buf-len N ]\n"
			  "		[ tcp-data-split auto|on|off ]\n"
			  "		[ cqe-size N ]\n"
			  "		[ tx-push on|off ]\n"
			  "		[ rx-push on|off ]\n"
			  "		[ tx-push-buf-len N]\n"
	},
	{
		.opts	= "-k|--show-features|--show-offload",
		.json	= true,
		.func	= do_gfeatures,
		.nlfunc	= nl_gfeatures,
		.help	= "Get state of protocol offload and other features"
	},
	{
		.opts	= "-K|--features|--offload",
		.func	= do_sfeatures,
		.nlfunc	= nl_sfeatures,
		.help	= "Set protocol offload and other features",
		.xhelp	= "		FEATURE on|off ...\n"
	},
	{
		.opts	= "-i|--driver",
		.func	= do_gdrv,
		.help	= "Show driver information"
	},
	{
		.opts	= "-d|--register-dump",
		.func	= do_gregs,
		.help	= "Do a register dump",
		.xhelp	= "		[ raw on|off ]\n"
			  "		[ file FILENAME ]\n"
	},
	{
		.opts	= "-e|--eeprom-dump",
		.func	= do_geeprom,
		.help	= "Do a EEPROM dump",
		.xhelp	= "		[ raw on|off ]\n"
			  "		[ offset N ]\n"
			  "		[ length N ]\n"
	},
	{
		.opts	= "-E|--change-eeprom",
		.func	= do_seeprom,
		.help	= "Change bytes in device EEPROM",
		.xhelp	= "		[ magic N ]\n"
			  "		[ offset N ]\n"
			  "		[ length N ]\n"
			  "		[ value N ]\n"
	},
	{
		.opts	= "-r|--negotiate",
		.func	= do_nway_rst,
		.help	= "Restart N-WAY negotiation"
	},
	{
		.opts	= "-p|--identify",
		.func	= do_phys_id,
		.help	= "Show visible port identification (e.g. blinking)",
		.xhelp	= "		[ TIME-IN-SECONDS ]\n"
	},
	{
		.opts	= "-t|--test",
		.func	= do_test,
		.help	= "Execute adapter self test",
		.xhelp	= "		[ online | offline | external_lb ]\n"
	},
	{
		.opts	= "-S|--statistics",
		.json	= true,
		.func	= do_gnicstats,
		.nlchk	= nl_gstats_chk,
		.nlfunc	= nl_gstats,
		.help	= "Show adapter statistics",
		.xhelp	= "		[ --all-groups | --groups [eth-phy] [eth-mac] [eth-ctrl] [rmon] ]\n"
			  "		[ --src aggregate | emac | pmac ]\n"
	},
	{
		.opts	= "--phy-statistics",
		.func	= do_gphystats,
		.help	= "Show phy statistics"
	},
	{
		.opts	= "-n|-u|--show-nfc|--show-ntuple",
		.func	= do_grxclass,
		.help	= "Show Rx network flow classification options or rules",
		.xhelp	= "		[ rx-flow-hash tcp4|udp4|ah4|esp4|sctp4|"
			  "tcp6|udp6|ah6|esp6|sctp6 [context %d] |\n"
			  "		  rule %d ]\n"
	},
	{
		.opts	= "-N|-U|--config-nfc|--config-ntuple",
		.func	= do_srxclass,
		.help	= "Configure Rx network flow classification options or rules",
		.xhelp	= "		rx-flow-hash tcp4|udp4|ah4|esp4|sctp4|"
			  "tcp6|udp6|ah6|esp6|sctp6 m|v|t|s|d|f|n|r... [context %d] |\n"
			  "		flow-type ether|ip4|tcp4|udp4|sctp4|ah4|esp4|"
			  "ip6|tcp6|udp6|ah6|esp6|sctp6\n"
			  "			[ src %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n"
			  "			[ dst %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n"
			  "			[ proto %d [m %x] ]\n"
			  "			[ src-ip IP-ADDRESS [m IP-ADDRESS] ]\n"
			  "			[ dst-ip IP-ADDRESS [m IP-ADDRESS] ]\n"
			  "			[ tos %d [m %x] ]\n"
			  "			[ tclass %d [m %x] ]\n"
			  "			[ l4proto %d [m %x] ]\n"
			  "			[ src-port %d [m %x] ]\n"
			  "			[ dst-port %d [m %x] ]\n"
			  "			[ spi %d [m %x] ]\n"
			  "			[ vlan-etype %x [m %x] ]\n"
			  "			[ vlan %x [m %x] ]\n"
			  "			[ user-def %x [m %x] ]\n"
			  "			[ dst-mac %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n"
			  "			[ action %d ] | [ vf %d queue %d ]\n"
			  "			[ context %d ]\n"
			  "			[ loc %d ] |\n"
			  "		delete %d\n"
	},
	{
		.opts	= "-T|--show-time-stamping",
		.func	= do_tsinfo,
		.nlfunc	= nl_tsinfo,
		.help	= "Show time stamping capabilities"
	},
	{
		.opts	= "-x|--show-rxfh-indir|--show-rxfh",
		.json	= true,
		.func	= do_grxfh,
		.nlfunc	= nl_grss,
		.help	= "Show Rx flow hash indirection table and/or RSS hash key",
		.xhelp	= "		[ context %d ]\n"
	},
	{
		.opts	= "-X|--set-rxfh-indir|--rxfh",
		.func	= do_srxfh,
		.help	= "Set Rx flow hash indirection table and/or RSS hash key",
		.xhelp	= "		[ context %d|new ]\n"
			  "		[ equal N | weight W0 W1 ... | default ]\n"
			  "		[ hkey %x:%x:%x:%x:%x:.... ]\n"
			  "		[ hfunc FUNC ]\n"
			  "		[ delete ]\n"
	},
	{
		.opts	= "-f|--flash",
		.func	= do_flash,
		.help	= "Flash firmware image from the specified file to a region on the device",
		.xhelp	= "		FILENAME [ REGION-NUMBER-TO-FLASH ]\n"
	},
	{
		.opts	= "-P|--show-permaddr",
		.func	= do_permaddr,
		.nlfunc	= nl_permaddr,
		.help	= "Show permanent hardware address"
	},
	{
		.opts	= "-w|--get-dump",
		.func	= do_getfwdump,
		.help	= "Get dump flag, data",
		.xhelp	= "		[ data FILENAME ]\n"
	},
	{
		.opts	= "-W|--set-dump",
		.func	= do_setfwdump,
		.help	= "Set dump flag of the device",
		.xhelp	= "		N\n"
	},
	{
		.opts	= "-l|--show-channels",
		.func	= do_gchannels,
		.nlfunc	= nl_gchannels,
		.help	= "Query Channels"
	},
	{
		.opts	= "-L|--set-channels",
		.func	= do_schannels,
		.nlfunc	= nl_schannels,
		.help	= "Set Channels",
		.xhelp	= "		[ rx N ]\n"
			  "		[ tx N ]\n"
			  "		[ other N ]\n"
			  "		[ combined N ]\n"
	},
	{
		.opts	= "--show-priv-flags",
		.func	= do_gprivflags,
		.nlfunc	= nl_gprivflags,
		.help	= "Query private flags"
	},
	{
		.opts	= "--set-priv-flags",
		.func	= do_sprivflags,
		.nlfunc	= nl_sprivflags,
		.help	= "Set private flags",
		.xhelp	= "		FLAG on|off ...\n"
	},
	{
		.opts	= "-m|--dump-module-eeprom|--module-info",
		.func	= do_getmodule,
		.nlfunc = nl_getmodule,
		.help	= "Query/Decode Module EEPROM information and optical diagnostics if available",
		.xhelp	= "		[ raw on|off ]\n"
			  "		[ hex on|off ]\n"
			  "		[ offset N ]\n"
			  "		[ length N ]\n"
			  "		[ page N ]\n"
			  "		[ bank N ]\n"
			  "		[ i2c N ]\n"
	},
	{
		.opts	= "--show-eee",
		.func	= do_geee,
		.nlfunc	= nl_geee,
		.help	= "Show EEE settings",
	},
	{
		.opts	= "--set-eee",
		.func	= do_seee,
		.nlfunc	= nl_seee,
		.help	= "Set EEE settings",
		.xhelp	= "		[ eee on|off ]\n"
			  "		[ advertise %x ]\n"
			  "		[ tx-lpi on|off ]\n"
			  "		[ tx-timer %d ]\n"
	},
	{
		.opts	= "--set-phy-tunable",
		.func	= do_set_phy_tunable,
		.help	= "Set PHY tunable",
		.xhelp	= "		[ downshift on|off [count N] ]\n"
			  "		[ fast-link-down on|off [msecs N] ]\n"
			  "		[ energy-detect-power-down on|off [msecs N] ]\n"
	},
	{
		.opts	= "--get-phy-tunable",
		.func	= do_get_phy_tunable,
		.help	= "Get PHY tunable",
		.xhelp	= "		[ downshift ]\n"
			  "		[ fast-link-down ]\n"
			  "		[ energy-detect-power-down ]\n"
	},
	{
		.opts	= "--get-tunable",
		.func	= do_gtunable,
		.help	= "Get tunable",
		.xhelp	= "		[ rx-copybreak ]\n"
			  "		[ tx-copybreak ]\n"
			  "		[ tx-buf-size ]\n"
			  "		[ pfc-prevention-tout ]\n"
	},
	{
		.opts	= "--set-tunable",
		.func	= do_stunable,
		.help	= "Set tunable",
		.xhelp	= "		[ rx-copybreak N ]\n"
			  "		[ tx-copybreak N ]\n"
			  "		[ tx-buf-size N ]\n"
			  "		[ pfc-prevention-tout N ]\n"
	},
	{
		.opts	= "--reset",
		.func	= do_reset,
		.help	= "Reset components",
		.xhelp	= "		[ flags %x ]\n"
			  "		[ mgmt ]\n"
			  "		[ mgmt-shared ]\n"
			  "		[ irq ]\n"
			  "		[ irq-shared ]\n"
			  "		[ dma ]\n"
			  "		[ dma-shared ]\n"
			  "		[ filter ]\n"
			  "		[ filter-shared ]\n"
			  "		[ offload ]\n"
			  "		[ offload-shared ]\n"
			  "		[ mac ]\n"
			  "		[ mac-shared ]\n"
			  "		[ phy ]\n"
			  "		[ phy-shared ]\n"
			  "		[ ram ]\n"
			  "		[ ram-shared ]\n"
			  "		[ ap ]\n"
			  "		[ ap-shared ]\n"
			  "		[ dedicated ]\n"
			  "		[ all ]\n"
	},
	{
		.opts	= "--show-fec",
		.json	= true,
		.func	= do_gfec,
		.nlfunc	= nl_gfec,
		.help	= "Show FEC settings",
	},
	{
		.opts	= "--set-fec",
		.func	= do_sfec,
		.nlfunc	= nl_sfec,
		.help	= "Set FEC settings",
		.xhelp	= "		[ encoding auto|off|rs|baser|llrs [...] ]\n"
	},
	{
		.opts	= "-Q|--per-queue",
		.func	= do_perqueue,
		.help	= "Apply per-queue command. ",
		.xhelp	= "The supported sub commands include --show-coalesce, --coalesce"
			  "		[queue_mask %x] SUB_COMMAND\n",
	},
	{
		.opts	= "--cable-test",
		.json	= true,
		.nlfunc	= nl_cable_test,
		.help	= "Perform a cable test",
	},
	{
		.opts	= "--cable-test-tdr",
		.json	= true,
		.nlfunc	= nl_cable_test_tdr,
		.help	= "Print cable test time domain reflectrometery data",
		.xhelp	= "		[ first N ]\n"
			  "		[ last N ]\n"
			  "		[ step N ]\n"
			  "		[ pair N ]\n"
	},
	{
		.opts	= "--show-tunnels",
		.nlfunc	= nl_gtunnels,
		.help	= "Show NIC tunnel offload information",
	},
	{
		.opts	= "--show-module",
		.json	= true,
		.nlfunc	= nl_gmodule,
		.help	= "Show transceiver module settings",
	},
	{
		.opts	= "--set-module",
		.nlfunc	= nl_smodule,
		.help	= "Set transceiver module settings",
		.xhelp	= "		[ power-mode-policy high|auto ]\n"
	},
	{
		.opts	= "--get-plca-cfg",
		.nlfunc	= nl_plca_get_cfg,
		.help	= "Get PLCA configuration",
	},
	{
		.opts	= "--set-plca-cfg",
		.nlfunc	= nl_plca_set_cfg,
		.help	= "Set PLCA configuration",
		.xhelp  = "		[ enable on|off ]\n"
			  "		[ node-id N ]\n"
			  "		[ node-cnt N ]\n"
			  "		[ to-tmr N ]\n"
			  "		[ burst-cnt N ]\n"
			  "		[ burst-tmr N ]\n"
	},
	{
		.opts	= "--get-plca-status",
		.nlfunc	= nl_plca_get_status,
		.help	= "Get PLCA status information",
	},
	{
		.opts	= "--show-mm",
		.json	= true,
		.nlfunc	= nl_get_mm,
		.help	= "Show MAC merge layer state",
	},
	{
		.opts	= "--set-mm",
		.nlfunc	= nl_set_mm,
		.help	= "Set MAC merge layer parameters",
			  "		[ verify-enabled on|off ]\n"
			  "		[ verify-time N ]\n"
			  "		[ tx-enabled on|off ]\n"
			  "		[ pmac-enabled on|off ]\n"
			  "		[ tx-min-frag-size 60-252 ]\n"
	},
	{
		.opts	= "--show-pse",
		.json	= true,
		.nlfunc	= nl_gpse,
		.help	= "Show settings for Power Sourcing Equipment",
	},
	{
		.opts	= "--set-pse",
		.nlfunc	= nl_spse,
		.help	= "Set Power Sourcing Equipment settings",
		.xhelp	= "		[ podl-pse-admin-control enable|disable ]\n"
	},
	{
		.opts	= "-h|--help",
		.no_dev	= true,
		.func	= show_usage,
		.help	= "Show this help"
	},
	{
		.opts	= "--version",
		.no_dev	= true,
		.func	= do_version,
		.help	= "Show version number"
	},
	{}
};

static int show_usage(struct cmd_context *ctx __maybe_unused)
{
	int i;

	/* ethtool -h */
	fprintf(stdout, PACKAGE " version " VERSION "\n");
	fprintf(stdout,	"Usage:\n");
	for (i = 0; args[i].opts; i++) {
		fputs("        ethtool [ FLAGS ] ", stdout);
		fprintf(stdout, "%s %s\t%s\n",
			args[i].opts,
			args[i].no_dev ? "\t" : "DEVNAME",
			args[i].help);
		if (args[i].xhelp)
			fputs(args[i].xhelp, stdout);
	}
	nl_monitor_usage();
	fprintf(stdout, "\n");
	fprintf(stdout, "FLAGS:\n");
	fprintf(stdout, "	--debug MASK	turn on debugging messages\n");
	fprintf(stdout, "	--json		enable JSON output format (not supported by all commands)\n");
	fprintf(stdout, "	-I|--include-statistics		request device statistics related to the command (not supported by all commands)\n");

	return 0;
}

static int find_option(char *arg)
{
	const char *opt;
	size_t len;
	int k;

	for (k = 1; args[k].opts; k++) {
		opt = args[k].opts;
		for (;;) {
			len = strcspn(opt, "|");
			if (strncmp(arg, opt, len) == 0 && arg[len] == 0)
				return k;

			if (opt[len] == 0)
				break;
			opt += len + 1;
		}
	}

	return -1;
}

#define MAX(x, y) (x > y ? x : y)

static int find_max_num_queues(struct cmd_context *ctx)
{
	struct ethtool_channels echannels;

	echannels.cmd = ETHTOOL_GCHANNELS;
	if (send_ioctl(ctx, &echannels))
		return -1;

	return MAX(echannels.rx_count, echannels.tx_count) +
		echannels.combined_count;
}

static struct ethtool_per_queue_op *
get_per_queue_coalesce(struct cmd_context *ctx, __u32 *queue_mask, int n_queues)
{
	struct ethtool_per_queue_op *per_queue_opt;

	per_queue_opt = malloc(sizeof(*per_queue_opt) + n_queues *
			sizeof(struct ethtool_coalesce));
	if (!per_queue_opt)
		return NULL;

	memcpy(per_queue_opt->queue_mask, queue_mask,
	       __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32) * sizeof(__u32));
	per_queue_opt->cmd = ETHTOOL_PERQUEUE;
	per_queue_opt->sub_command = ETHTOOL_GCOALESCE;
	if (send_ioctl(ctx, per_queue_opt)) {
		free(per_queue_opt);
		perror("Cannot get device per queue parameters");
		return NULL;
	}

	return per_queue_opt;
}

static void set_per_queue_coalesce(struct cmd_context *ctx,
				   struct ethtool_per_queue_op *per_queue_opt,
				   int n_queues)
{
	struct ethtool_coalesce ecoal;
	DECLARE_COALESCE_OPTION_VARS();
	struct cmdline_info cmdline_coalesce[] = COALESCE_CMDLINE_INFO(ecoal);
	__u32 *queue_mask = per_queue_opt->queue_mask;
	struct ethtool_coalesce *ecoal_q;
	int gcoalesce_changed = 0;
	int i, idx = 0;

	parse_generic_cmdline(ctx, &gcoalesce_changed,
			      cmdline_coalesce, ARRAY_SIZE(cmdline_coalesce));

	ecoal_q = (struct ethtool_coalesce *)(per_queue_opt + 1);
	for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); i++) {
		int queue = i * 32;
		__u32 mask = queue_mask[i];

		while (mask > 0) {
			if (mask & 0x1) {
				int changed = 0;

				memcpy(&ecoal, ecoal_q + idx,
				       sizeof(struct ethtool_coalesce));
				do_generic_set(cmdline_coalesce,
					       ARRAY_SIZE(cmdline_coalesce),
					       &changed);
				if (!changed)
					fprintf(stderr,
						"Queue %d, no coalesce parameters changed\n",
						queue);
				memcpy(ecoal_q + idx, &ecoal,
				       sizeof(struct ethtool_coalesce));
				idx++;
			}
			mask = mask >> 1;
			queue++;
		}
		if (idx == n_queues)
			break;
	}

	per_queue_opt->cmd = ETHTOOL_PERQUEUE;
	per_queue_opt->sub_command = ETHTOOL_SCOALESCE;

	if (send_ioctl(ctx, per_queue_opt))
		perror("Cannot set device per queue parameters");
}

static int do_perqueue(struct cmd_context *ctx)
{
	struct ethtool_per_queue_op *per_queue_opt;
	__u32 queue_mask[__KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32)] = {0};
	int i, n_queues = 0;

	if (ctx->argc == 0)
		exit_bad_args();

	/*
	 * The sub commands will be applied to
	 * all queues if no queue_mask set
	 */
	if (strncmp(*ctx->argp, "queue_mask", 11)) {
		n_queues = find_max_num_queues(ctx);
		if (n_queues < 0) {
			perror("Cannot get number of queues");
			return -EFAULT;
		} else if (n_queues > MAX_NUM_QUEUE) {
			n_queues = MAX_NUM_QUEUE;
		}
		for (i = 0; i < n_queues / 32; i++)
			queue_mask[i] = ~0;
		if (n_queues % 32)
			queue_mask[i] = (1 << (n_queues - i * 32)) - 1;
		fprintf(stdout,
			"The sub commands will be applied to all %d queues\n",
			n_queues);
	} else {
		if (ctx->argc <= 2)
			exit_bad_args();
		ctx->argc--;
		ctx->argp++;
		if (parse_hex_u32_bitmap(*ctx->argp, MAX_NUM_QUEUE,
		    queue_mask)) {
			fprintf(stdout, "Invalid queue mask\n");
			return -1;
		}
		for (i = 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); i++) {
			__u32 mask = queue_mask[i];

			while (mask > 0) {
				if (mask & 0x1)
					n_queues++;
				mask = mask >> 1;
			}
		}
		ctx->argc--;
		ctx->argp++;
	}

	i = find_option(ctx->argp[0]);
	if (i < 0)
		exit_bad_args();

	if (strstr(args[i].opts, "--show-coalesce") != NULL) {
		per_queue_opt = get_per_queue_coalesce(ctx, queue_mask,
						       n_queues);
		if (per_queue_opt == NULL) {
			perror("Cannot get device per queue parameters");
			return -EFAULT;
		}
		dump_per_queue_coalesce(per_queue_opt, queue_mask, n_queues);
		free(per_queue_opt);
	} else if (strstr(args[i].opts, "--coalesce") != NULL) {
		ctx->argc--;
		ctx->argp++;
		per_queue_opt = get_per_queue_coalesce(ctx, queue_mask,
						       n_queues);
		if (per_queue_opt == NULL) {
			perror("Cannot get device per queue parameters");
			return -EFAULT;
		}
		set_per_queue_coalesce(ctx, per_queue_opt, n_queues);
		free(per_queue_opt);
	} else {
		perror("The subcommand is not supported yet");
		return -EOPNOTSUPP;
	}

	return 0;
}

static int ioctl_init(struct cmd_context *ctx, bool no_dev)
{
	if (no_dev) {
		ctx->fd = -1;
		return 0;
	}
	if (strlen(ctx->devname) >= IFNAMSIZ) {
		fprintf(stderr, "Device name longer than %u characters\n",
			IFNAMSIZ - 1);
		exit_bad_args();
	}

	/* Setup our control structures. */
	memset(&ctx->ifr, 0, sizeof(ctx->ifr));
	strcpy(ctx->ifr.ifr_name, ctx->devname);

	/* Open control socket. */
	ctx->fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (ctx->fd < 0)
		ctx->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
	if (ctx->fd < 0) {
		perror("Cannot get control socket");
		return 70;
	}

	return 0;
}

int main(int argc, char **argp)
{
	struct cmd_context ctx = {};
	int ret;
	int k;

	init_global_link_mode_masks();

	if (argc < 2)
		exit_bad_args();

	/* Skip command name */
	argp++;
	argc--;

	while (true) {
		if (*argp && !strcmp(*argp, "--debug")) {
			char *eptr;

			if (argc < 2)
				exit_bad_args();
			ctx.debug = strtoul(argp[1], &eptr, 0);
			if (!argp[1][0] || *eptr)
				exit_bad_args();

			argp += 2;
			argc -= 2;
			continue;
		}
		if (*argp && !strcmp(*argp, "--json")) {
			ctx.json = true;
			argp += 1;
			argc -= 1;
			continue;
		}
		if (*argp && (!strcmp(*argp, "--include-statistics") ||
			      !strcmp(*argp, "-I"))) {
			ctx.show_stats = true;
			argp += 1;
			argc -= 1;
			continue;
		}
		break;
	}
	if (*argp && !strcmp(*argp, "--monitor")) {
		ctx.argp = ++argp;
		ctx.argc = --argc;
		ret = nl_monitor(&ctx);
		return ret ? 1 : 0;
	}

	/* First argument must be either a valid option or a device
	 * name to get settings for (which we don't expect to begin
	 * with '-').
	 */
	if (!*argp)
		exit_bad_args();

	k = find_option(*argp);
	if (k > 0) {
		argp++;
		argc--;
	} else {
		if ((*argp)[0] == '-')
			exit_bad_args();
		k = 0;
	}

	if (!args[k].no_dev) {
		ctx.devname = *argp++;
		argc--;

		if (!ctx.devname)
			exit_bad_args();
	}
	if (ctx.json && !args[k].json)
		exit_bad_args();
	ctx.argc = argc;
	ctx.argp = argp;
	netlink_run_handler(&ctx, args[k].nlchk, args[k].nlfunc, !args[k].func);

	if (ctx.json) /* no IOCTL command supports JSON output */
		exit_bad_args();

	ret = ioctl_init(&ctx, args[k].no_dev);
	if (ret)
		return ret;

	return args[k].func(&ctx);
}
  
Raju Lakkaraju March 4, 2024, 11:20 a.m. UTC | #5
Hi Andrew, 

For Ethtool netlink, we need to change the "ethnl_set_wol( )" to add option is incremental.
Can you please check those changes OK or not.

Thanks,
Raju.

> -----Original Message-----
> From: Raju Lakkaraju - I30499
> Sent: Friday, March 1, 2024 3:52 PM
> To: Andrew Lunn <andrew@lunn.ch>
> Cc: netdev@vger.kernel.org; davem@davemloft.net; kuba@kernel.org; linux-
> kernel@vger.kernel.org; Bryan Whitehead - C21958
> <Bryan.Whitehead@microchip.com>; richardcochran@gmail.com;
> UNGLinuxDriver <UNGLinuxDriver@microchip.com>
> Subject: RE: [PATCH net 3/3] net: lan743x: Address problems with wake option
> flags configuration sequences
> 
> Hi Andrew,
> 
> > -----Original Message-----
> > From: Andrew Lunn <andrew@lunn.ch>
> > Sent: Thursday, February 29, 2024 8:36 PM
> > To: Raju Lakkaraju - I30499 <Raju.Lakkaraju@microchip.com>
> > Cc: netdev@vger.kernel.org; davem@davemloft.net; kuba@kernel.org;
> > linux- kernel@vger.kernel.org; Bryan Whitehead - C21958
> > <Bryan.Whitehead@microchip.com>; richardcochran@gmail.com;
> > UNGLinuxDriver <UNGLinuxDriver@microchip.com>
> > Subject: Re: [PATCH net 3/3] net: lan743x: Address problems with wake
> > option flags configuration sequences
> >
> > EXTERNAL EMAIL: Do not click links or open attachments unless you know
> > the content is safe
> >
> > On Thu, Feb 29, 2024 at 08:59:20AM +0000, Raju.Lakkaraju@microchip.com
> > wrote:
> > > Hi Andrew,
> > >
> > > Thank you for review comments.
> > >
> > > > -----Original Message-----
> > > > From: Andrew Lunn <andrew@lunn.ch>
> > > > Sent: Tuesday, February 27, 2024 7:28 AM
> > > > To: Raju Lakkaraju - I30499 <Raju.Lakkaraju@microchip.com>
> > > > Cc: netdev@vger.kernel.org; davem@davemloft.net; kuba@kernel.org;
> > > > linux- kernel@vger.kernel.org; Bryan Whitehead - C21958
> > > > <Bryan.Whitehead@microchip.com>; richardcochran@gmail.com;
> > > > UNGLinuxDriver <UNGLinuxDriver@microchip.com>
> > > > Subject: Re: [PATCH net 3/3] net: lan743x: Address problems with
> > > > wake option flags configuration sequences
> > > >
> > > > EXTERNAL EMAIL: Do not click links or open attachments unless you
> > > > know the content is safe
> > > >
> > > > On Mon, Feb 26, 2024 at 01:39:34PM +0530, Raju Lakkaraju wrote:
> > > > > Wake options handling has been reworked as follows:
> > > > > a. We only enable secure on magic packet when both secure and
> > > > > magic
> > wol
> > > > >    options are requested together.
> > > > > b. If secure-on magic packet had been previously enabled, and a
> > > > subsequent
> > > > >    command does not include it, we add it. This was done to
> > > > > workaround
> > a
> > > > >    problem with the 'pm-suspend' application which is unaware of
> > secure-on
> > > > >    magic packet being enabled and can unintentionally disable it prior
> to
> > > > >    putting the system into suspend.
> > > >
> > > > This seems to be in a bit of a grey area. But ethtool says:
> > > >
> > > >            wol p|u|m|b|a|g|s|f|d...
> > > >                   Sets  Wake-on-LAN  options.   Not  all devices support this.
> > > >                   The argument to this option is a string of characters speci‐
> > > >                   fying which options to enable.
> > > >                   p   Wake on PHY activity
> > > >                   u   Wake on unicast messages
> > > >                   m   Wake on multicast messages
> > > >                   b   Wake on broadcast messages
> > > >                   a   Wake on ARP
> > > >                   g   Wake on MagicPacket™
> > > >                   s   Enable SecureOn™ password for MagicPacket™
> > > >                   f   Wake on filter(s)
> > > >                   d   Disable (wake on  nothing).   This  option
> > > >                       clears all previous options.
> > > >
> > > > d clears everything. All other things enable one option. There
> > > > does not appear to be a way to disable a single option, and i
> > > > would say, adding options is incremental.
> > > >
> > >
> > > Yes. "d" clears everything.
> > > But, if we enable "g" then enable "a", "g" option overwritten by "a"
> >
> > This is where i say it is a bit of a grey area. For me, they are
> > incremental. You can enable a and then later enable g, and you should have
> both enabled.
> >
> 
> Yes. But, it's "Ethtool" issue.
> Even though ethtool get the WOL configuration before to set, over written
> with wanted wol request configuration.
> 
> > > Please find the attached log information
> > > > So:
> > > >
> > > > > a. We only enable secure on magic packet when both secure and
> > > > > magic
> > wol
> > > > >    options are requested together.
> > > >
> > > > I don't think they need to be requested together. I think you can
> > > > first enable Wake on MagicPacket and then in a second call to
> > > > ethtool Enable SecureOn password for MagicPacket. I also don't
> > > > think it would unreasonable to accept Enable SecureOn password for
> > > > MagicPacket and have that imply Wake on MagicPacket.
> > > >
> > >
> > > If we need to enable any 2 options, we have to provide both options
> > together.
> > > i.e.
> > > sudo ethtool -s enp9s0 wol ga
> >
> > which i think is wrong. A driver should allow incremental adding WoL
> options.
> >
> 
> Ok.
> 
> > >
> > > > And:
> > > >
> > > > > b. If secure-on magic packet had been previously enabled, and a
> > > > subsequent
> > > > >    command does not include it, we add it.
> > > >
> > > > If there has not been a d, secure-on magic packet should remain
> > > > enabled until there is a d.
> > > >
> > >
> > > This is not happened with existing "Ethtool".
> > > Please find the log information in an attachment file.
> >
> > That could just be a driver bug.
> >
> > Take a step back. Is there any clear documentation about how ethtool
> > -s wol should really work? Any comments in the code? Any man page
> > documentation.
> >
> 
> I'm not able to find  any more "ethtool" documentations other than ethtool
> man page.
> 
> I have gone through the ethtool application code.
> (git://git.kernel.org/pub/scm/network/ethtool/ethtool.git) and check the WOL
> options.
> 
> In ioctl method (Not netlink), do_sset( ) function, before set the parameters,
> first get the WOL configuration.
> (i.e. in ethtool.c file, Line no. 3294)
> 
> Then, assign/overwrite WOL wanted config to wolopts parameter.
> 
> Existing Code:  From Line 3290 -  3312
> ################################################################
> #######
> 3290         if (gwol_changed) {
> 3291                 struct ethtool_wolinfo wol;
> 3292
> 3293                 wol.cmd = ETHTOOL_GWOL;
> 3294                 err = send_ioctl(ctx, &wol);
> 3295                 if (err < 0) {
> 3296                         perror("Cannot get current wake-on-lan settings");
> 3297                 } else {
> 3298                         /* Change everything the user specified. */
> 3299                         if (wol_change)
> 3300                                 wol.wolopts = wol_wanted;
> 3301                         if (sopass_change) {
> 3302                                 int i;
> 3303                                 for (i = 0; i < SOPASS_MAX; i++)
> 3304                                         wol.sopass[i] = sopass_wanted[i];
> 3305                         }
> 3306
> 3307                         /* Try to perform the update. */
> 3308                         wol.cmd = ETHTOOL_SWOL;
> 3309                         err = send_ioctl(ctx, &wol);
> 3310                         if (err < 0)
> 3311                                 perror("Cannot set new wake-on-lan settings");
> 3312                 }
> ################################################################
> #######
> Current issue is overwriting wolopts with wol_wanted i.e.
> 3300                                 wol.wolopts = wol_wanted;
> 
> Instead of over write, Need to "OR" the wolopts and wol_wanted i.e.
> 3300                                 wol.wolopts |= wol_wanted
> 
> Final code changes should be:
> ################################################################
> #######
> 3290         if (gwol_changed) {
> 3291                 struct ethtool_wolinfo wol;
> 3292
> 3293                 wol.cmd = ETHTOOL_GWOL;
> 3294                 err = send_ioctl(ctx, &wol);
> 3295                 if (err < 0) {
> 3296                         perror("Cannot get current wake-on-lan settings");
> 3297                 } else {
> 3298                         /* Change everything the user specified. */
> 3299                         if (wol_change  && wol_wanted)
> 3300                                 wol.wolopts |= wol_wanted;
> 		     else if (wol_change  && !wol_wanted)
> 			wol.wolopts = 0
> 3301                         if (sopass_change) {
> 3302                                 int i;
> 3303                                 for (i = 0; i < SOPASS_MAX; i++)
> 3304                                         wol.sopass[i] = sopass_wanted[i];
> 3305                         }
> 3306
> 3307                         /* Try to perform the update. */
> 3308                         wol.cmd = ETHTOOL_SWOL;
> 3309                         err = send_ioctl(ctx, &wol);
> 3310                         if (err < 0)
> 3311                                 perror("Cannot set new wake-on-lan settings");
> 3312                 }
> ################################################################
> #######
> 
> Similar changes require in netlink functions also.
> 

For Netlink:

--- a/net/ethtool/wol.c
+++ b/net/ethtool/wol.c
@@ -107,16 +107,26 @@ ethnl_set_wol(struct ethnl_req_info *req_info, struct genl_info *info)
        struct ethtool_wolinfo wol = { .cmd = ETHTOOL_GWOL };
        struct net_device *dev = req_info->dev;
        struct nlattr **tb = info->attrs;
+       __u32 wolopts = 0;
        bool mod = false;
        int ret;
 
        dev->ethtool_ops->get_wol(dev, &wol);
-       ret = ethnl_update_bitset32(&wol.wolopts, WOL_MODE_COUNT,
+       ret = ethnl_update_bitset32(&wolopts, WOL_MODE_COUNT,
                                    tb[ETHTOOL_A_WOL_MODES], wol_mode_names,
                                    info->extack, &mod);
        if (ret < 0)
                return ret;
-       if (wol.wolopts & ~wol.supported) {
+
+       if (wolopts) {
+               wol.wolopts |= wolopts;
+       } else {
+               wol.wolopts = 0;
+               memset(&wol.sopass, 0, sizeof(wol.sopass));
+               mod = true;
+       }
+
+       if (wolopts & ~wol.supported) {
                NL_SET_ERR_MSG_ATTR(info->extack, tb[ETHTOOL_A_WOL_MODES],
                                    "cannot enable unsupported WoL mode");
                return -EINVAL;
###############################################################

Please find the an attachment file.

> > Lets first understand how it is expected to work. Then we can decided
> > if the driver is implementing it correctly.
> >
> >    Andrew
> 
> Please find the "ethtool" application code main file as attachment file.
> 
> Thanks,
> Raju
  

Patch

diff --git a/drivers/net/ethernet/microchip/lan743x_ethtool.c b/drivers/net/ethernet/microchip/lan743x_ethtool.c
index 4899582b3d1d..cda4df2ac1cd 100644
--- a/drivers/net/ethernet/microchip/lan743x_ethtool.c
+++ b/drivers/net/ethernet/microchip/lan743x_ethtool.c
@@ -1165,6 +1165,16 @@  static int lan743x_ethtool_set_wol(struct net_device *netdev,
 	struct lan743x_adapter *adapter = netdev_priv(netdev);
 	int ret;
 
+	if (wol->wolopts && wol->wolopts != adapter->wolopts &&
+	    adapter->wolopts & WAKE_MAGICSECURE) {
+		wol->wolopts |= WAKE_MAGICSECURE;
+		netif_warn(adapter, drv, adapter->netdev,
+			   "Ensure secure-on magic packet remains enabled if not explicitly disabled\n");
+	}
+
+	if ((wol->wolopts & WAKE_MAGICSECURE) && !(wol->wolopts & WAKE_MAGIC))
+		return -EINVAL;
+
 	if (netdev->phydev) {
 		ret = phy_ethtool_set_wol(netdev->phydev, wol);
 		if (ret != -EOPNOTSUPP && ret != 0)