summaryrefslogtreecommitdiffstats
path: root/src/net/netdevice.c
blob: 59861d05dcbe9438512f6fa243169854550a2158 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
/*
 * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdint.h>
#include <byteswap.h>
#include <string.h>
#include <errno.h>
#include <gpxe/if_ether.h>
#include <gpxe/pkbuff.h>
#include <gpxe/tables.h>
#include <gpxe/netdevice.h>

/** @file
 *
 * Network device management
 *
 */

/**
 * Static single instance of a network device
 *
 * The gPXE API is designed to accommodate multiple network devices.
 * However, in the interests of code size, the implementation behind
 * the API supports only a single instance of a network device.
 *
 * No code outside of netdevice.c should ever refer directly to @c
 * static_single_netdev.
 *
 * Callers should always check the return status of alloc_netdev(),
 * register_netdev() etc.  In the current implementation this code
 * will be optimised out by the compiler, so there is no penalty.
 */
struct net_device static_single_netdev;

/** Registered network-layer protocols */
static struct net_protocol net_protocols[0] __table_start ( net_protocols );
static struct net_protocol net_protocols_end[0] __table_end ( net_protocols );

/** Network-layer addresses for @c static_single_netdev */
static struct net_address static_single_netdev_addresses[0]
	__table_start ( sgl_netdev_addresses );
static struct net_address static_single_netdev_addresses_end[0]
	__table_end ( sgl_netdev_addresses );

/** Recevied packet queue */
static LIST_HEAD ( rx_queue );

/**
 * Add packet to receive queue
 *
 * @v netdev		Network device
 * @v pkb		Packet buffer
 *
 * The packet is added to the RX queue.  Ownership of the packet is
 * transferred to the RX queue; the caller must not touch the packet
 * buffer after calling netdev_rx().
 */
void netdev_rx ( struct net_device *netdev, struct pk_buff *pkb ) {
	DBG ( "Packet received\n" );
	pkb->ll_protocol = netdev->ll_protocol;
	list_add_tail ( &pkb->list, &rx_queue );
}

/**
 * Identify network protocol
 *
 * @v net_proto		Network-layer protocol, in network-byte order
 * @ret net_protocol	Network-layer protocol, or NULL
 *
 * Identify a network-layer protocol from a protocol number, which
 * must be an ETH_P_XXX constant in network-byte order.
 */
struct net_protocol * find_net_protocol ( uint16_t net_proto ) {
	struct net_protocol *net_protocol;

	for ( net_protocol = net_protocols ; net_protocol < net_protocols_end ;
	      net_protocol++ ) {
		if ( net_protocol->net_proto == net_proto )
			return net_protocol;
	}
	return NULL;
}

/**
 * Identify network device by network-layer address
 *
 * @v net_protocol	Network-layer protocol
 * @v net_addr		Network-layer address
 * @ret netdev		Network device, or NULL
 *
 * Searches through all network devices to find the device with the
 * specified network-layer address.
 *
 * Note that even with a static single network device, this function
 * can still return NULL.
 */
struct net_device *
find_netdev_by_net_addr ( struct net_protocol *net_protocol,
			  void *net_addr ) {
	struct net_address *net_address;
	struct net_device *netdev = &static_single_netdev;
	
	for ( net_address = static_single_netdev_addresses ;
	      net_address < static_single_netdev_addresses_end ;
	      net_address ++ ) {
		if ( ( net_address->net_protocol == net_protocol ) &&
		     ( memcmp ( net_address->net_addr, net_addr,
				net_protocol->net_addr_len ) == 0 ) )
			return netdev;
	}

	return NULL;
}

/**
 * Transmit packet via a network device
 *
 * @v pkb		Packet buffer
 * @v netdev		Network device, or NULL
 * @ret rc		Return status code
 *
 * Transmits the packet via the specified network device.  The packet
 * must begin with a network-layer header, and the @c net_protocol
 * field must have been filled in.  If @c netdev is NULL, the network
 * device is identified via the packet contents, if possible.  If this
 * function returns success, it has taken ownership of the packet
 * buffer.
 */
int net_transmit_via ( struct pk_buff *pkb, struct net_device *netdev ) {
	struct net_protocol *net_protocol;
	struct net_header nethdr;
	struct ll_protocol *ll_protocol;
	struct ll_header llhdr;
	int rc;

	/* Perform network-layer routing */
	net_protocol = pkb->net_protocol;
	nethdr.net_protocol = net_protocol;
	if ( ( rc = net_protocol->route ( pkb, &nethdr ) ) != 0 ) {
		DBG ( "Could not route to %s address %s\n",
		      net_protocol->name,
		      net_protocol->ntoa ( nethdr.dest_net_addr ) );
		return rc;
	}

	/* Identify transmitting network device, if not specified */
	if ( ! netdev ) {
		netdev = find_netdev_by_net_addr ( net_protocol,
						   nethdr.source_net_addr );
		if ( ! netdev ) {
			DBG ( "No network device for %s address %s\n",
			      net_protocol->name,
			      net_protocol->ntoa ( nethdr.source_net_addr ) );
			return -EHOSTUNREACH;
		}
	}

	/* Perform link-layer routing */
	ll_protocol = netdev->ll_protocol;
	llhdr.ll_protocol = ll_protocol;
	if ( ( rc = ll_protocol->route ( netdev, &nethdr, &llhdr ) ) != 0 ) {
		DBG ( "No link-layer route to %s address %s\n",
		      net_protocol->name,
		      net_protocol->ntoa ( nethdr.dest_net_addr ) );
		return rc;
	}

	/* Prepend link-layer header */
	pkb_push ( pkb, ll_protocol->ll_header_len );
	ll_protocol->fill_llh ( &llhdr, pkb );

	/* Transmit packet */
	if ( ( rc = netdev->transmit ( netdev, pkb ) ) != 0 ) {
		DBG ( "Device failed to transmit packet\n" );
		return rc;
	}
	
	DBG ( "Packet transmitted\n" );
	return 0;
}

/**
 * Transmit packet
 *
 * @v pkb		Packet buffer
 * @ret rc		Return status code
 *
 * Transmits the packet via the appropriate network device.  If this
 * function returns success, it has taken ownership of the packet
 * buffer.
 */
int net_transmit ( struct pk_buff *pkb ) {
	return net_transmit_via ( pkb, NULL );
}

/**
 * Poll for packet on all network devices
 *
 * @ret True		There are packets present in the receive queue
 * @ret False		There are no packets present in the receive queue
 *
 * Polls all network devices for received packets.  Any received
 * packets will be added to the RX packet queue via netdev_rx().
 */
int net_poll ( void ) {
	struct net_device *netdev = &static_single_netdev;

	DBG ( "Polling network\n" );
	netdev->poll ( netdev );

	return ( ! list_empty ( &rx_queue ) );
}

/**
 * Remove packet from receive queue
 *
 * @ret pkb		Packet buffer, or NULL
 *
 * Removes the first packet from the RX queue and returns it.
 * Ownership of the packet is transferred to the caller.
 */
struct pk_buff * net_rx_dequeue ( void ) {
	struct pk_buff *pkb;

	list_for_each_entry ( pkb, &rx_queue, list ) {
		list_del ( &pkb->list );
		return pkb;
	}
	return NULL;
}

void net_run ( void ) {
	struct pk_buff *pkb;
	struct ll_protocol *ll_protocol;
	struct ll_header llhdr;
	struct net_protocol *net_protocol;

	while ( ( pkb = net_rx_dequeue () ) ) {

		/* Parse link-layer header */
		ll_protocol = pkb->ll_protocol;
		ll_protocol->parse_llh ( pkb, &llhdr );

		/* Identify network-layer protocol */
		net_protocol = find_net_protocol ( llhdr.net_proto );
		if ( ! net_protocol ) {
			DBG ( "Unknown network-layer protocol %x\n",
			      ntohs ( llhdr.net_proto ) );
			free_pkb ( pkb );
			continue;
		}
		pkb->net_protocol = net_protocol;

		/* Strip off link-layer header */
		pkb_pull ( pkb, ll_protocol->ll_header_len );

		/* Hand off to network layer */
		if ( net_protocol->rx ( pkb ) != 0 ) {
			DBG ( "Network-layer protocol refused packet\n" );
			free_pkb ( pkb );
			continue;
		}

		DBG ( "Processed received packet\n" );
	}
}