xref: /linux/drivers/net/phy/linkmode.c (revision 95298d63c67673c654c08952672d016212b26054)
1 // SPDX-License-Identifier: GPL-2.0+
2 #include <linux/linkmode.h>
3 
4 /**
5  * linkmode_resolve_pause - resolve the allowable pause modes
6  * @local_adv: local advertisement in ethtool format
7  * @partner_adv: partner advertisement in ethtool format
8  * @tx_pause: pointer to bool to indicate whether transmit pause should be
9  * enabled.
10  * @rx_pause: pointer to bool to indicate whether receive pause should be
11  * enabled.
12  *
13  * Flow control is resolved according to our and the link partners
14  * advertisements using the following drawn from the 802.3 specs:
15  *  Local device  Link partner
16  *  Pause AsymDir Pause AsymDir Result
17  *    0     X       0     X     Disabled
18  *    0     1       1     0     Disabled
19  *    0     1       1     1     TX
20  *    1     0       0     X     Disabled
21  *    1     X       1     X     TX+RX
22  *    1     1       0     1     RX
23  */
24 void linkmode_resolve_pause(const unsigned long *local_adv,
25 			    const unsigned long *partner_adv,
26 			    bool *tx_pause, bool *rx_pause)
27 {
28 	__ETHTOOL_DECLARE_LINK_MODE_MASK(m);
29 
30 	linkmode_and(m, local_adv, partner_adv);
31 	if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, m)) {
32 		*tx_pause = true;
33 		*rx_pause = true;
34 	} else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, m)) {
35 		*tx_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
36 					      partner_adv);
37 		*rx_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
38 					      local_adv);
39 	} else {
40 		*tx_pause = false;
41 		*rx_pause = false;
42 	}
43 }
44 EXPORT_SYMBOL_GPL(linkmode_resolve_pause);
45 
46 /**
47  * linkmode_set_pause - set the pause mode advertisement
48  * @advertisement: advertisement in ethtool format
49  * @tx: boolean from ethtool struct ethtool_pauseparam tx_pause member
50  * @rx: boolean from ethtool struct ethtool_pauseparam rx_pause member
51  *
52  * Configure the advertised Pause and Asym_Pause bits according to the
53  * capabilities of provided in @tx and @rx.
54  *
55  * We convert as follows:
56  *  tx rx  Pause AsymDir
57  *  0  0   0     0
58  *  0  1   1     1
59  *  1  0   0     1
60  *  1  1   1     0
61  *
62  * Note: this translation from ethtool tx/rx notation to the advertisement
63  * is actually very problematical. Here are some examples:
64  *
65  * For tx=0 rx=1, meaning transmit is unsupported, receive is supported:
66  *
67  *  Local device  Link partner
68  *  Pause AsymDir Pause AsymDir Result
69  *    1     1       1     0     TX + RX - but we have no TX support.
70  *    1     1       0     1	Only this gives RX only
71  *
72  * For tx=1 rx=1, meaning we have the capability to transmit and receive
73  * pause frames:
74  *
75  *  Local device  Link partner
76  *  Pause AsymDir Pause AsymDir Result
77  *    1     0       0     1     Disabled - but since we do support tx and rx,
78  *				this should resolve to RX only.
79  *
80  * Hence, asking for:
81  *  rx=1 tx=0 gives Pause+AsymDir advertisement, but we may end up
82  *            resolving to tx+rx pause or only rx pause depending on
83  *            the partners advertisement.
84  *  rx=0 tx=1 gives AsymDir only, which will only give tx pause if
85  *            the partners advertisement allows it.
86  *  rx=1 tx=1 gives Pause only, which will only allow tx+rx pause
87  *            if the other end also advertises Pause.
88  */
89 void linkmode_set_pause(unsigned long *advertisement, bool tx, bool rx)
90 {
91 	linkmode_mod_bit(ETHTOOL_LINK_MODE_Pause_BIT, advertisement, rx);
92 	linkmode_mod_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, advertisement,
93 			 rx ^ tx);
94 }
95 EXPORT_SYMBOL_GPL(linkmode_set_pause);
96