1 /*
2  * Copyright (C) 2010 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301, USA.
18  *
19  * You can also choose to distribute this program under the terms of
20  * the Unmodified Binary Distribution Licence (as given in the file
21  * COPYING.UBDL), provided that you have satisfied its requirements.
22  */
23 
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25 
26 #include <stdlib.h>
27 #include <string.h>
28 #include <byteswap.h>
29 #include <errno.h>
30 #include <ipxe/timer.h>
31 #include <ipxe/iobuf.h>
32 #include <ipxe/netdevice.h>
33 #include <ipxe/if_ether.h>
34 #include <ipxe/ethernet.h>
35 #include <ipxe/eth_slow.h>
36 
37 /** @file
38  *
39  * Ethernet slow protocols
40  *
41  * We implement a very simple passive LACP entity, that pretends that
42  * each port is the only port on an individual system.  We avoid the
43  * need for timeout logic (and retaining local state about our
44  * partner) by requesting the same timeout period (1s or 30s) as our
45  * partner requests, and then simply responding to every packet the
46  * partner sends us.
47  */
48 
49 struct net_protocol eth_slow_protocol __net_protocol;
50 
51 /** Slow protocols multicast address */
52 static const uint8_t eth_slow_address[ETH_ALEN] =
53 	{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x02 };
54 
55 /**
56  * Name LACP TLV type
57  *
58  * @v type		LACP TLV type
59  * @ret name		Name of LACP TLV type
60  */
61 static inline __attribute__ (( always_inline )) const char *
eth_slow_lacp_tlv_name(uint8_t type)62 eth_slow_lacp_tlv_name ( uint8_t type ) {
63 	switch ( type ) {
64 	case ETH_SLOW_TLV_TERMINATOR:		return "terminator";
65 	case ETH_SLOW_TLV_LACP_ACTOR:		return "actor";
66 	case ETH_SLOW_TLV_LACP_PARTNER:		return "partner";
67 	case ETH_SLOW_TLV_LACP_COLLECTOR:	return "collector";
68 	default:				return "<invalid>";
69 	}
70 }
71 
72 /**
73  * Name marker TLV type
74  *
75  * @v type		Marker TLV type
76  * @ret name		Name of marker TLV type
77  */
78 static inline __attribute__ (( always_inline )) const char *
eth_slow_marker_tlv_name(uint8_t type)79 eth_slow_marker_tlv_name ( uint8_t type ) {
80 	switch ( type ) {
81 	case ETH_SLOW_TLV_TERMINATOR:		return "terminator";
82 	case ETH_SLOW_TLV_MARKER_REQUEST:	return "request";
83 	case ETH_SLOW_TLV_MARKER_RESPONSE:	return "response";
84 	default:				return "<invalid>";
85 	}
86 }
87 
88 /**
89  * Name LACP state
90  *
91  * @v state		LACP state
92  * @ret name		LACP state name
93  */
eth_slow_lacp_state_name(uint8_t state)94 static const char * eth_slow_lacp_state_name ( uint8_t state ) {
95 	static char state_chars[] = "AFGSCDLX";
96 	unsigned int i;
97 
98 	for ( i = 0 ; i < 8 ; i++ ) {
99 		state_chars[i] |= 0x20;
100 		if ( state & ( 1 << i ) )
101 			state_chars[i] &= ~0x20;
102 	}
103 	return state_chars;
104 }
105 
106 /**
107  * Dump LACP packet
108  *
109  * @v iobuf		I/O buffer
110  * @v netdev		Network device
111  * @v label		"RX" or "TX"
112  */
eth_slow_lacp_dump(struct io_buffer * iobuf,struct net_device * netdev,const char * label)113 static void eth_slow_lacp_dump ( struct io_buffer *iobuf,
114 				 struct net_device *netdev,
115 				 const char *label ) {
116 	union eth_slow_packet *eth_slow = iobuf->data;
117 	struct eth_slow_lacp *lacp = &eth_slow->lacp;
118 
119 	DBGC ( netdev,
120 	       "SLOW %s %s LACP actor (%04x,%s,%04x,%02x,%04x) [%s]\n",
121 	       netdev->name, label, ntohs ( lacp->actor.system_priority ),
122 	       eth_ntoa ( lacp->actor.system ),
123 	       ntohs ( lacp->actor.key ),
124 	       ntohs ( lacp->actor.port_priority ),
125 	       ntohs ( lacp->actor.port ),
126 	       eth_slow_lacp_state_name ( lacp->actor.state ) );
127 	DBGC ( netdev,
128 	       "SLOW %s %s LACP partner (%04x,%s,%04x,%02x,%04x) [%s]\n",
129 	       netdev->name, label, ntohs ( lacp->partner.system_priority ),
130 	       eth_ntoa ( lacp->partner.system ),
131 	       ntohs ( lacp->partner.key ),
132 	       ntohs ( lacp->partner.port_priority ),
133 	       ntohs ( lacp->partner.port ),
134 	       eth_slow_lacp_state_name ( lacp->partner.state ) );
135 	DBGC ( netdev, "SLOW %s %s LACP collector %04x (%d us)\n",
136 	       netdev->name, label, ntohs ( lacp->collector.max_delay ),
137 	       ( ntohs ( lacp->collector.max_delay ) * 10 ) );
138 	DBGC2_HDA ( netdev, 0, iobuf->data, iob_len ( iobuf ) );
139 }
140 
141 /**
142  * Process incoming LACP packet
143  *
144  * @v iobuf		I/O buffer
145  * @v netdev		Network device
146  * @ret rc		Return status code
147  */
eth_slow_lacp_rx(struct io_buffer * iobuf,struct net_device * netdev)148 static int eth_slow_lacp_rx ( struct io_buffer *iobuf,
149 			      struct net_device *netdev ) {
150 	union eth_slow_packet *eth_slow = iobuf->data;
151 	struct eth_slow_lacp *lacp = &eth_slow->lacp;
152 	unsigned int interval;
153 
154 	eth_slow_lacp_dump ( iobuf, netdev, "RX" );
155 
156 	/* Check for looped-back packets */
157 	if ( memcmp ( lacp->actor.system, netdev->ll_addr,
158 		      sizeof ( lacp->actor.system ) ) == 0 ) {
159 		DBGC ( netdev, "SLOW %s RX loopback detected\n",
160 		       netdev->name );
161 		return -ELOOP;
162 	}
163 
164 	/* If partner is not in sync, collecting, and distributing,
165 	 * then block the link until after the next expected LACP
166 	 * packet.
167 	 */
168 	if ( ~lacp->actor.state & ( LACP_STATE_IN_SYNC |
169 				    LACP_STATE_COLLECTING |
170 				    LACP_STATE_DISTRIBUTING ) ) {
171 		DBGC ( netdev, "SLOW %s LACP partner is down\n", netdev->name );
172 		interval = ( ( lacp->actor.state & LACP_STATE_FAST ) ?
173 			     ( ( LACP_INTERVAL_FAST + 1 ) * TICKS_PER_SEC ) :
174 			     ( ( LACP_INTERVAL_SLOW + 1 ) * TICKS_PER_SEC ) );
175 		netdev_link_block ( netdev, interval );
176 	} else {
177 		if ( netdev_link_blocked ( netdev ) ) {
178 			DBGC ( netdev, "SLOW %s LACP partner is up\n",
179 			       netdev->name );
180 		}
181 		netdev_link_unblock ( netdev );
182 	}
183 
184 	/* Build response */
185 	memset ( lacp->reserved, 0, sizeof ( lacp->reserved ) );
186 	memset ( &lacp->terminator, 0, sizeof ( lacp->terminator ) );
187 	memset ( &lacp->collector, 0, sizeof ( lacp->collector ) );
188 	lacp->collector.tlv.type = ETH_SLOW_TLV_LACP_COLLECTOR;
189 	lacp->collector.tlv.length = ETH_SLOW_TLV_LACP_COLLECTOR_LEN;
190 	memcpy ( &lacp->partner, &lacp->actor, sizeof ( lacp->partner ) );
191 	lacp->partner.tlv.type = ETH_SLOW_TLV_LACP_PARTNER;
192 	lacp->partner.tlv.length = ETH_SLOW_TLV_LACP_PARTNER_LEN;
193 	memset ( &lacp->partner.reserved, 0,
194 		 sizeof ( lacp->partner.reserved ) );
195 	memset ( &lacp->actor, 0, sizeof ( lacp->actor ) );
196 	lacp->actor.tlv.type = ETH_SLOW_TLV_LACP_ACTOR;
197 	lacp->actor.tlv.length = ETH_SLOW_TLV_LACP_ACTOR_LEN;
198 	lacp->actor.system_priority = htons ( LACP_SYSTEM_PRIORITY_MAX );
199 	memcpy ( lacp->actor.system, netdev->ll_addr,
200 		 sizeof ( lacp->actor.system ) );
201 	lacp->actor.key = htons ( 1 );
202 	lacp->actor.port_priority = htons ( LACP_PORT_PRIORITY_MAX );
203 	lacp->actor.port = htons ( 1 );
204 	lacp->actor.state = ( LACP_STATE_AGGREGATABLE |
205 			      LACP_STATE_IN_SYNC |
206 			      LACP_STATE_COLLECTING |
207 			      LACP_STATE_DISTRIBUTING |
208 			      ( lacp->partner.state & LACP_STATE_FAST ) );
209 	lacp->header.version = ETH_SLOW_LACP_VERSION;
210 
211 	/* Send response */
212 	eth_slow_lacp_dump ( iobuf, netdev, "TX" );
213 	return net_tx ( iobuf, netdev, &eth_slow_protocol, eth_slow_address,
214 			netdev->ll_addr );
215 }
216 
217 /**
218  * Dump marker packet
219  *
220  * @v iobuf		I/O buffer
221  * @v netdev		Network device
222  * @v label		"RX" or "TX"
223  */
eth_slow_marker_dump(struct io_buffer * iobuf,struct net_device * netdev,const char * label)224 static void eth_slow_marker_dump ( struct io_buffer *iobuf,
225 				   struct net_device *netdev,
226 				   const char *label ) {
227 	union eth_slow_packet *eth_slow = iobuf->data;
228 	struct eth_slow_marker *marker = &eth_slow->marker;
229 
230 	DBGC ( netdev, "SLOW %s %s marker %s port %04x system %s xact %08x\n",
231 	       netdev->name, label,
232 	       eth_slow_marker_tlv_name ( marker->marker.tlv.type ),
233 	       ntohs ( marker->marker.port ),
234 	       eth_ntoa ( marker->marker.system ),
235 	       ntohl ( marker->marker.xact ) );
236 	DBGC2_HDA ( netdev, 0, iobuf->data, iob_len ( iobuf ) );
237 }
238 
239 /**
240  * Process incoming marker packet
241  *
242  * @v iobuf		I/O buffer
243  * @v netdev		Network device
244  * @ret rc		Return status code
245  */
eth_slow_marker_rx(struct io_buffer * iobuf,struct net_device * netdev)246 static int eth_slow_marker_rx ( struct io_buffer *iobuf,
247 				struct net_device *netdev ) {
248 	union eth_slow_packet *eth_slow = iobuf->data;
249 	struct eth_slow_marker *marker = &eth_slow->marker;
250 
251 	eth_slow_marker_dump ( iobuf, netdev, "RX" );
252 
253 	if ( marker->marker.tlv.type == ETH_SLOW_TLV_MARKER_REQUEST ) {
254 		/* Send marker response */
255 		marker->marker.tlv.type = ETH_SLOW_TLV_MARKER_RESPONSE;
256 		eth_slow_marker_dump ( iobuf, netdev, "TX" );
257 		return net_tx ( iobuf, netdev, &eth_slow_protocol,
258 				eth_slow_address, netdev->ll_addr );
259 	} else {
260 		/* Discard all other marker packets */
261 		free_iob ( iobuf );
262 		return -EINVAL;
263 	}
264 }
265 
266 /**
267  * Process incoming slow packet
268  *
269  * @v iobuf		I/O buffer
270  * @v netdev		Network device
271  * @v ll_dest		Link-layer destination address
272  * @v ll_source		Link-layer source address
273  * @v flags		Packet flags
274  * @ret rc		Return status code
275  */
eth_slow_rx(struct io_buffer * iobuf,struct net_device * netdev,const void * ll_dest __unused,const void * ll_source __unused,unsigned int flags __unused)276 static int eth_slow_rx ( struct io_buffer *iobuf,
277 			 struct net_device *netdev,
278 			 const void *ll_dest __unused,
279 			 const void *ll_source __unused,
280 			 unsigned int flags __unused ) {
281 	union eth_slow_packet *eth_slow = iobuf->data;
282 
283 	/* Sanity checks */
284 	if ( iob_len ( iobuf ) < sizeof ( *eth_slow ) ) {
285 		free_iob ( iobuf );
286 		return -EINVAL;
287 	}
288 
289 	/* Strip any trailing padding */
290 	iob_unput ( iobuf, ( sizeof ( *eth_slow ) - iob_len ( iobuf ) ) );
291 
292 	/* Handle according to subtype */
293 	switch ( eth_slow->header.subtype ) {
294 	case ETH_SLOW_SUBTYPE_LACP:
295 		return eth_slow_lacp_rx ( iobuf, netdev );
296 	case ETH_SLOW_SUBTYPE_MARKER:
297 		return eth_slow_marker_rx ( iobuf, netdev );
298 	default:
299 		DBGC ( netdev, "SLOW %s RX unknown subtype %02x\n",
300 		       netdev->name, eth_slow->header.subtype );
301 		free_iob ( iobuf );
302 		return -EINVAL;
303 	}
304 }
305 
306 /** Slow protocol */
307 struct net_protocol eth_slow_protocol __net_protocol = {
308 	.name = "Slow",
309 	.net_proto = htons ( ETH_P_SLOW ),
310 	.rx = eth_slow_rx,
311 };
312