xref: /openbmc/linux/drivers/net/phy/linkmode.c (revision 45c767fa)
1a87ae8a9SRussell King // SPDX-License-Identifier: GPL-2.0+
2a87ae8a9SRussell King #include <linux/linkmode.h>
3a87ae8a9SRussell King 
4a87ae8a9SRussell King /**
5a87ae8a9SRussell King  * linkmode_resolve_pause - resolve the allowable pause modes
6a87ae8a9SRussell King  * @local_adv: local advertisement in ethtool format
7a87ae8a9SRussell King  * @partner_adv: partner advertisement in ethtool format
8a87ae8a9SRussell King  * @tx_pause: pointer to bool to indicate whether transmit pause should be
9a87ae8a9SRussell King  * enabled.
10a87ae8a9SRussell King  * @rx_pause: pointer to bool to indicate whether receive pause should be
11a87ae8a9SRussell King  * enabled.
12a87ae8a9SRussell King  *
13a87ae8a9SRussell King  * Flow control is resolved according to our and the link partners
14a87ae8a9SRussell King  * advertisements using the following drawn from the 802.3 specs:
15a87ae8a9SRussell King  *  Local device  Link partner
16a87ae8a9SRussell King  *  Pause AsymDir Pause AsymDir Result
17a87ae8a9SRussell King  *    0     X       0     X     Disabled
18a87ae8a9SRussell King  *    0     1       1     0     Disabled
19a87ae8a9SRussell King  *    0     1       1     1     TX
20a87ae8a9SRussell King  *    1     0       0     X     Disabled
21a87ae8a9SRussell King  *    1     X       1     X     TX+RX
22a87ae8a9SRussell King  *    1     1       0     1     RX
23a87ae8a9SRussell King  */
linkmode_resolve_pause(const unsigned long * local_adv,const unsigned long * partner_adv,bool * tx_pause,bool * rx_pause)24a87ae8a9SRussell King void linkmode_resolve_pause(const unsigned long *local_adv,
25a87ae8a9SRussell King 			    const unsigned long *partner_adv,
26a87ae8a9SRussell King 			    bool *tx_pause, bool *rx_pause)
27a87ae8a9SRussell King {
28a87ae8a9SRussell King 	__ETHTOOL_DECLARE_LINK_MODE_MASK(m);
29a87ae8a9SRussell King 
30a87ae8a9SRussell King 	linkmode_and(m, local_adv, partner_adv);
31a87ae8a9SRussell King 	if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, m)) {
32a87ae8a9SRussell King 		*tx_pause = true;
33a87ae8a9SRussell King 		*rx_pause = true;
34a87ae8a9SRussell King 	} else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, m)) {
35a87ae8a9SRussell King 		*tx_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
36a87ae8a9SRussell King 					      partner_adv);
37a87ae8a9SRussell King 		*rx_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
38a87ae8a9SRussell King 					      local_adv);
39a87ae8a9SRussell King 	} else {
40a87ae8a9SRussell King 		*tx_pause = false;
41a87ae8a9SRussell King 		*rx_pause = false;
42a87ae8a9SRussell King 	}
43a87ae8a9SRussell King }
44a87ae8a9SRussell King EXPORT_SYMBOL_GPL(linkmode_resolve_pause);
4545c767faSRussell King 
4645c767faSRussell King /**
4745c767faSRussell King  * linkmode_set_pause - set the pause mode advertisement
4845c767faSRussell King  * @advertisement: advertisement in ethtool format
4945c767faSRussell King  * @tx: boolean from ethtool struct ethtool_pauseparam tx_pause member
5045c767faSRussell King  * @rx: boolean from ethtool struct ethtool_pauseparam rx_pause member
5145c767faSRussell King  *
5245c767faSRussell King  * Configure the advertised Pause and Asym_Pause bits according to the
5345c767faSRussell King  * capabilities of provided in @tx and @rx.
5445c767faSRussell King  *
5545c767faSRussell King  * We convert as follows:
5645c767faSRussell King  *  tx rx  Pause AsymDir
5745c767faSRussell King  *  0  0   0     0
5845c767faSRussell King  *  0  1   1     1
5945c767faSRussell King  *  1  0   0     1
6045c767faSRussell King  *  1  1   1     0
6145c767faSRussell King  *
6245c767faSRussell King  * Note: this translation from ethtool tx/rx notation to the advertisement
6345c767faSRussell King  * is actually very problematical. Here are some examples:
6445c767faSRussell King  *
6545c767faSRussell King  * For tx=0 rx=1, meaning transmit is unsupported, receive is supported:
6645c767faSRussell King  *
6745c767faSRussell King  *  Local device  Link partner
6845c767faSRussell King  *  Pause AsymDir Pause AsymDir Result
6945c767faSRussell King  *    1     1       1     0     TX + RX - but we have no TX support.
7045c767faSRussell King  *    1     1       0     1	Only this gives RX only
7145c767faSRussell King  *
7245c767faSRussell King  * For tx=1 rx=1, meaning we have the capability to transmit and receive
7345c767faSRussell King  * pause frames:
7445c767faSRussell King  *
7545c767faSRussell King  *  Local device  Link partner
7645c767faSRussell King  *  Pause AsymDir Pause AsymDir Result
7745c767faSRussell King  *    1     0       0     1     Disabled - but since we do support tx and rx,
7845c767faSRussell King  *				this should resolve to RX only.
7945c767faSRussell King  *
8045c767faSRussell King  * Hence, asking for:
8145c767faSRussell King  *  rx=1 tx=0 gives Pause+AsymDir advertisement, but we may end up
8245c767faSRussell King  *            resolving to tx+rx pause or only rx pause depending on
8345c767faSRussell King  *            the partners advertisement.
8445c767faSRussell King  *  rx=0 tx=1 gives AsymDir only, which will only give tx pause if
8545c767faSRussell King  *            the partners advertisement allows it.
8645c767faSRussell King  *  rx=1 tx=1 gives Pause only, which will only allow tx+rx pause
8745c767faSRussell King  *            if the other end also advertises Pause.
8845c767faSRussell King  */
linkmode_set_pause(unsigned long * advertisement,bool tx,bool rx)8945c767faSRussell King void linkmode_set_pause(unsigned long *advertisement, bool tx, bool rx)
9045c767faSRussell King {
9145c767faSRussell King 	linkmode_mod_bit(ETHTOOL_LINK_MODE_Pause_BIT, advertisement, rx);
9245c767faSRussell King 	linkmode_mod_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, advertisement,
9345c767faSRussell King 			 rx ^ tx);
9445c767faSRussell King }
9545c767faSRussell King EXPORT_SYMBOL_GPL(linkmode_set_pause);
96