1+ #!/usr/bin/env python3
2+ """
3+ Interface Speed Duplex (Copper)
4+
5+ Verify that auto-negotiation results in expected speed/duplex mode.
6+ """
7+
8+ import infamy
9+ import subprocess
10+ from infamy .util import until
11+
12+ ADVERTISE_MODES = {
13+ # Values from ethtool's ETHTOOL_LINK_MODE bit positions
14+ # See: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/ethtool.h
15+ "10half" : 0x0001 ,
16+ "10full" : 0x0002 ,
17+ "100half" : 0x0004 ,
18+ "100full" : 0x0008 ,
19+ "1000full" : 0x0020
20+ }
21+
22+ def advertise_host_modes (interface , modes ):
23+ mask = 0
24+ for mode in modes :
25+ mask |= ADVERTISE_MODES [mode ]
26+ try :
27+ subprocess .run ([
28+ "ethtool" , "-s" , interface ,
29+ "advertise" , hex (mask )
30+ ], check = True )
31+ except subprocess .CalledProcessError as e :
32+ raise RuntimeError (f"Failed to advertise modes via ethtool: { e } " )
33+
34+ def get_target_speed_duplex (target , interface ):
35+ data = target .get_data (f"/ietf-interfaces:interfaces/interface[name='{ interface } ']" ) \
36+ ["interfaces" ]["interface" ][interface ]
37+ eth = data .get ("ethernet" , {})
38+
39+ return eth .get ("speed" ), eth .get ("duplex" )
40+
41+ def set_target_speed_duplex (target , interface , speed , duplex ):
42+ target .put_config_dicts ({
43+ "ietf-interfaces" : {
44+ "interfaces" : {
45+ "interface" : [{
46+ "name" : interface ,
47+ "ethernet" : {
48+ "auto-negotiation" : {
49+ "enable" : False
50+ },
51+ "speed" : speed / 1000 ,
52+ "duplex" : duplex
53+ }
54+ }]
55+ }
56+ }
57+ })
58+
59+ def set_host_speed_duplex (interface , speed , duplex ):
60+ try :
61+ subprocess .run ([
62+ "ethtool" , "-s" , interface ,
63+ "speed" , str (speed ),
64+ "duplex" , duplex ,
65+ "autoneg" , "off"
66+ ], check = True )
67+ except subprocess .CalledProcessError as e :
68+ raise RuntimeError (f"Failed to set speed/duplex via ethtool: { e } " )
69+
70+ def verify_speed_duplex (target , interface , exp_speed , exp_duplex ):
71+ until (lambda : speed_duplex_present (target , interface ))
72+ act_speed , act_duplex = get_target_speed_duplex (target , interface )
73+ if act_speed is None or act_duplex is None :
74+ print (f"Could not fetch speed or duplex from target for interface { interface } " )
75+ test .fail ()
76+
77+ exp_speed_gbps = exp_speed / 1000
78+ if float (act_speed ) != exp_speed_gbps :
79+ print (f"act_speed: { act_speed } , exp_speed: { exp_speed_gbps } " )
80+ test .fail ()
81+
82+ if act_duplex .lower () != exp_duplex .lower ():
83+ print (f"act_duplex: { act_duplex } , exp_duplex: { exp_duplex } " )
84+ test .fail ()
85+
86+ print (f"Verified: { interface } is operating at { act_speed } Gbps, { act_duplex } duplex" )
87+
88+ def speed_duplex_present (target , interface ):
89+ speed , duplex = get_target_speed_duplex (target , interface )
90+ return speed is not None and duplex is not None
91+
92+ def enable_target_interface (target , interface ):
93+ target .put_config_dicts ({
94+ "ietf-interfaces" : {
95+ "interfaces" : {
96+ "interface" : [{
97+ "name" : interface ,
98+ "enabled" : True
99+ }]
100+ }
101+ }
102+ })
103+
104+ def enable_target_autoneg (target , interface ):
105+ target .put_config_dicts ({
106+ "ietf-interfaces" : {
107+ "interfaces" : {
108+ "interface" : [{
109+ "name" : interface ,
110+ "ethernet" : {
111+ "auto-negotiation" : {
112+ "enable" : True
113+ }
114+ }
115+ }]
116+ }
117+ }
118+ })
119+
120+ def enable_host_autoneg (interface ):
121+ subprocess .run (["ethtool" , "-s" , interface , "autoneg" , "on" ], check = True )
122+
123+ def cleanup (target , hdata , tdata ):
124+ """
125+ Restore both host and target interfaces to autonegotiation mode
126+ to ensure clean state for future tests.
127+ """
128+
129+ print ("Restoring interfaces to default (autoneg on)" )
130+ try :
131+ enable_host_autoneg (hdata )
132+ except Exception as e :
133+ print (f"Host autoneg restore failed: { e } " )
134+ try :
135+ enable_target_interface (target , tdata )
136+ enable_target_autoneg (target , tdata )
137+ except Exception as e :
138+ print (f"Target autoneg restore failed: { e } " )
139+
140+ with infamy .Test () as test :
141+ with test .step ("Set up topology and attach to target DUT" ):
142+ env = infamy .Env ()
143+ target = env .attach ("target" , "mgmt" )
144+ _ , hdata = env .ltop .xlate ("host" , "data" )
145+ _ , tdata = env .ltop .xlate ("target" , "data" )
146+
147+ # Append a test cleanup function
148+ test .push_test_cleanup (lambda : cleanup (target , hdata , tdata ))
149+
150+ with test .step ("Enable target interface" ):
151+ enable_target_interface (target , tdata )
152+
153+ # Fixed mode tests
154+ with test .step ("Set fixed 10/full" ):
155+ set_host_speed_duplex (hdata , 10 , "full" )
156+ set_target_speed_duplex (target , tdata , 10 , "full" )
157+ verify_speed_duplex (target , tdata , 10 , "full" )
158+
159+ with test .step ("Set fixed 10/half" ):
160+ set_host_speed_duplex (hdata , 10 , "half" )
161+ set_target_speed_duplex (target , tdata , 10 , "half" )
162+ verify_speed_duplex (target , tdata , 10 , "half" )
163+
164+ with test .step ("Set fixed 100/full" ):
165+ set_host_speed_duplex (hdata , 100 , "full" )
166+ set_target_speed_duplex (target , tdata , 100 , "full" )
167+ verify_speed_duplex (target , tdata , 100 , "full" )
168+
169+ with test .step ("Set fixed 100/half" ):
170+ set_host_speed_duplex (hdata , 100 , "half" )
171+ set_target_speed_duplex (target , tdata , 100 , "half" )
172+ verify_speed_duplex (target , tdata , 100 , "half" )
173+
174+ # Auto-negotiation tests: host advertises, Infix negotiates
175+ with test .step ("Switch to auto-negotiation mode for target and host" ):
176+ enable_host_autoneg (hdata )
177+ enable_target_autoneg (target , tdata )
178+
179+ with test .step ("Configure host to advertise 10/Full only" ):
180+ advertise_host_modes (hdata , ["10full" ])
181+ verify_speed_duplex (target , tdata , 10 , "full" )
182+
183+ with test .step ("Configure host to advertise 10/Half only" ):
184+ advertise_host_modes (hdata , ["10half" ])
185+ verify_speed_duplex (target , tdata , 10 , "half" )
186+
187+ with test .step ("Configure host to advertise 100/Full only" ):
188+ advertise_host_modes (hdata , ["100full" ])
189+ verify_speed_duplex (target , tdata , 100 , "full" )
190+
191+ with test .step ("Configure host to advertise 100/Half only" ):
192+ advertise_host_modes (hdata , ["100half" ])
193+ verify_speed_duplex (target , tdata , 100 , "half" )
194+
195+ with test .step ("Configure host to advertise 10/half + 10/full + 100/half" ):
196+ advertise_host_modes (hdata , ["10half" , "10full" , "100half" ])
197+ verify_speed_duplex (target , tdata , 100 , "half" )
198+
199+ with test .step ("Configure host to advertise 10/half + 10/full + 100/half + 100/full + 1000/full" ):
200+ advertise_host_modes (hdata , ["10half" , "10full" , "100half" , "100full" , "1000full" ])
201+ verify_speed_duplex (target , tdata , 1000 , "full" )
202+
203+ test .succeed ()
0 commit comments