xref: /haiku/src/add-ons/kernel/network/devices/ethernet/ethernet.cpp (revision 9e25244c5e9051f6cd333820d6332397361abd6c)
1 /*
2  * Copyright 2006-2009, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Axel Dörfler, axeld@pinc-software.de
7  */
8 
9 
10 #include <ethernet.h>
11 
12 #include <ether_driver.h>
13 #include <net_buffer.h>
14 #include <net_device.h>
15 #include <net_stack.h>
16 
17 #include <lock.h>
18 #include <util/AutoLock.h>
19 #include <util/DoublyLinkedList.h>
20 
21 #include <KernelExport.h>
22 
23 #include <errno.h>
24 #include <net/if.h>
25 #include <net/if_dl.h>
26 #include <net/if_media.h>
27 #include <net/if_types.h>
28 #include <new>
29 #include <stdlib.h>
30 #include <string.h>
31 
32 
33 struct ethernet_device : net_device, DoublyLinkedListLinkImpl<ethernet_device> {
34 	int		fd;
35 	uint32	frame_size;
36 };
37 
38 static const bigtime_t kLinkCheckInterval = 1000000;
39 	// 1 second
40 
41 net_buffer_module_info *gBufferModule;
42 static net_stack_module_info *sStackModule;
43 
44 static mutex sListLock;
45 static DoublyLinkedList<ethernet_device> sCheckList;
46 static sem_id sLinkChangeSemaphore;
47 static thread_id sLinkCheckerThread;
48 
49 
50 static status_t
51 update_link_state(ethernet_device *device, bool notify = true)
52 {
53 	ether_link_state state;
54 	if (ioctl(device->fd, ETHER_GET_LINK_STATE, &state,
55 			sizeof(ether_link_state)) < 0) {
56 		// This device does not support retrieving the link
57 		return B_NOT_SUPPORTED;
58 	}
59 
60 	state.media |= IFM_ETHER;
61 		// make sure the media type is returned correctly
62 
63 	if (device->media != state.media
64 		|| device->link_quality != state.quality
65 		|| device->link_speed != state.speed) {
66 		device->media = state.media;
67 		device->link_quality = state.quality;
68 		device->link_speed = state.speed;
69 
70 		if (device->media & IFM_ACTIVE) {
71 			if ((device->flags & IFF_LINK) == 0) {
72 				dprintf("%s: link up, media 0x%0x quality %u speed %u\n",
73 					device->name, (unsigned int)device->media,
74 					(unsigned int)device->link_quality,
75 					(unsigned int)device->link_speed);
76 			}
77 			device->flags |= IFF_LINK;
78 		} else {
79 			if ((device->flags & IFF_LINK) != 0) {
80 				dprintf("%s: link down, media 0x%0x quality %u speed %u\n",
81 					device->name, (unsigned int)device->media,
82 					(unsigned int)device->link_quality,
83 					(unsigned int)device->link_speed);
84 			}
85 			device->flags &= ~IFF_LINK;
86 		}
87 
88 
89 		if (notify)
90 			sStackModule->device_link_changed(device);
91 	}
92 
93 	return B_OK;
94 }
95 
96 
97 static status_t
98 ethernet_link_checker(void *)
99 {
100 	while (true) {
101 		status_t status = acquire_sem_etc(sLinkChangeSemaphore, 1,
102 			B_RELATIVE_TIMEOUT, kLinkCheckInterval);
103 		if (status == B_BAD_SEM_ID)
104 			break;
105 
106 		MutexLocker _(sListLock);
107 
108 		if (sCheckList.IsEmpty())
109 			break;
110 
111 		// check link state of all existing devices
112 
113 		DoublyLinkedList<ethernet_device>::Iterator iterator
114 			= sCheckList.GetIterator();
115 		while (iterator.HasNext()) {
116 			update_link_state(iterator.Next());
117 		}
118 	}
119 
120 	return B_OK;
121 }
122 
123 
124 //	#pragma mark -
125 
126 
127 status_t
128 ethernet_init(const char *name, net_device **_device)
129 {
130 	// Make sure this is a device in /dev/net, but not the
131 	// networking (userland) stack driver.
132 	// Also make sure the user didn't pass a path like
133 	// /dev/net/../etc.
134 	if (strncmp(name, "/dev/net/", 9)
135 		|| !strcmp(name, "/dev/net/userland_server")
136 		|| strstr(name, "..") != NULL)
137 		return B_BAD_VALUE;
138 
139 	if (access(name, F_OK) != 0)
140 		return errno;
141 
142 	status_t status = get_module(NET_BUFFER_MODULE_NAME, (module_info **)&gBufferModule);
143 	if (status < B_OK)
144 		return status;
145 
146 	ethernet_device *device = new (std::nothrow) ethernet_device;
147 	if (device == NULL) {
148 		put_module(NET_BUFFER_MODULE_NAME);
149 		return B_NO_MEMORY;
150 	}
151 
152 	memset(device, 0, sizeof(ethernet_device));
153 
154 	strcpy(device->name, name);
155 	device->flags = IFF_BROADCAST | IFF_LINK;
156 	device->type = IFT_ETHER;
157 	device->mtu = 1500;
158 	device->media = IFM_ACTIVE | IFM_ETHER;
159 	device->header_length = ETHER_HEADER_LENGTH;
160 	device->fd = -1;
161 
162 	*_device = device;
163 	return B_OK;
164 }
165 
166 
167 status_t
168 ethernet_uninit(net_device *device)
169 {
170 	put_module(NET_BUFFER_MODULE_NAME);
171 	delete device;
172 
173 	return B_OK;
174 }
175 
176 
177 status_t
178 ethernet_up(net_device *_device)
179 {
180 	ethernet_device *device = (ethernet_device *)_device;
181 
182 	device->fd = open(device->name, O_RDWR);
183 	if (device->fd < 0)
184 		return errno;
185 
186 	uint64 dummy;
187 	if (ioctl(device->fd, ETHER_INIT, &dummy, sizeof(dummy)) < 0)
188 		goto err;
189 
190 	if (ioctl(device->fd, ETHER_GETADDR, device->address.data, ETHER_ADDRESS_LENGTH) < 0)
191 		goto err;
192 
193 	if (ioctl(device->fd, ETHER_GETFRAMESIZE, &device->frame_size, sizeof(uint32)) < 0) {
194 		// this call is obviously optional
195 		device->frame_size = ETHER_MAX_FRAME_SIZE;
196 	}
197 
198 	if (update_link_state(device, false) == B_OK) {
199 		// device supports retrieval of the link state
200 
201 		// Set the change notification semaphore; doesn't matter
202 		// if this is supported by the device or not
203 		ioctl(device->fd, ETHER_SET_LINK_STATE_SEM, &sLinkChangeSemaphore,
204 			sizeof(sem_id));
205 
206 		MutexLocker _(&sListLock);
207 
208 		if (sCheckList.IsEmpty()) {
209 			// start thread
210 			sLinkCheckerThread = spawn_kernel_thread(ethernet_link_checker,
211 				"ethernet link state checker", B_LOW_PRIORITY, NULL);
212 			if (sLinkCheckerThread >= B_OK)
213 				resume_thread(sLinkCheckerThread);
214 		}
215 
216 		sCheckList.Add(device);
217 	}
218 
219 	device->address.length = ETHER_ADDRESS_LENGTH;
220 	device->mtu = device->frame_size - device->header_length;
221 	return B_OK;
222 
223 err:
224 	close(device->fd);
225 	device->fd = -1;
226 	return errno;
227 }
228 
229 
230 void
231 ethernet_down(net_device *_device)
232 {
233 	ethernet_device *device = (ethernet_device *)_device;
234 
235 	MutexLocker _(sListLock);
236 
237 	// if the device is still part of the list, remove it
238 	if (device->GetDoublyLinkedListLink()->next != NULL
239 		|| device->GetDoublyLinkedListLink()->previous != NULL
240 		|| device == sCheckList.Head())
241 		sCheckList.Remove(device);
242 
243 	close(device->fd);
244 	device->fd = -1;
245 }
246 
247 
248 status_t
249 ethernet_control(net_device *_device, int32 op, void *argument,
250 	size_t length)
251 {
252 	ethernet_device *device = (ethernet_device *)_device;
253 	if (ioctl(device->fd, op, argument, length) < 0)
254 		return errno;
255 	return B_OK;
256 }
257 
258 
259 status_t
260 ethernet_send_data(net_device *_device, net_buffer *buffer)
261 {
262 	ethernet_device *device = (ethernet_device *)_device;
263 
264 //dprintf("try to send ethernet packet of %lu bytes (flags %ld):\n", buffer->size, buffer->flags);
265 	if (buffer->size > device->frame_size || buffer->size < ETHER_HEADER_LENGTH)
266 		return B_BAD_VALUE;
267 
268 	net_buffer *allocated = NULL;
269 	net_buffer *original = buffer;
270 
271 	if (gBufferModule->count_iovecs(buffer) > 1) {
272 		// TODO: for now, create a new buffer containing the data
273 		buffer = gBufferModule->duplicate(original);
274 		if (buffer == NULL)
275 			return ENOBUFS;
276 
277 		allocated = buffer;
278 
279 		if (gBufferModule->count_iovecs(buffer) > 1) {
280 			dprintf("scattered I/O is not yet supported by ethernet device.\n");
281 			gBufferModule->free(buffer);
282 			device->stats.send.errors++;
283 			return B_NOT_SUPPORTED;
284 		}
285 	}
286 
287 	struct iovec iovec;
288 	gBufferModule->get_iovecs(buffer, &iovec, 1);
289 
290 //dump_block((const char *)iovec.iov_base, buffer->size, "  ");
291 	ssize_t bytesWritten = write(device->fd, iovec.iov_base, iovec.iov_len);
292 //dprintf("sent: %ld\n", bytesWritten);
293 
294 	if (bytesWritten < 0) {
295 		device->stats.send.errors++;
296 		if (allocated)
297 			gBufferModule->free(allocated);
298 		return errno;
299 	}
300 
301 	device->stats.send.packets++;
302 	device->stats.send.bytes += bytesWritten;
303 
304 	gBufferModule->free(original);
305 	if (allocated)
306 		gBufferModule->free(allocated);
307 	return B_OK;
308 }
309 
310 
311 status_t
312 ethernet_receive_data(net_device *_device, net_buffer **_buffer)
313 {
314 	ethernet_device *device = (ethernet_device *)_device;
315 
316 	if (device->fd == -1)
317 		return B_FILE_ERROR;
318 
319 	// TODO: better header space
320 	net_buffer *buffer = gBufferModule->create(256);
321 	if (buffer == NULL)
322 		return ENOBUFS;
323 
324 	// TODO: this only works for standard ethernet frames - we need iovecs
325 	//	for jumbo frame support (or a separate read buffer)!
326 	//	It would be even nicer to get net_buffers from the ethernet driver
327 	//	directly.
328 
329 	ssize_t bytesRead;
330 	void *data;
331 
332 	status_t status = gBufferModule->append_size(buffer, device->frame_size, &data);
333 	if (status == B_OK && data == NULL) {
334 		dprintf("scattered I/O is not yet supported by ethernet device.\n");
335 		status = B_NOT_SUPPORTED;
336 	}
337 	if (status < B_OK)
338 		goto err;
339 
340 	bytesRead = read(device->fd, data, device->frame_size);
341 	if (bytesRead < 0) {
342 		device->stats.receive.errors++;
343 		status = errno;
344 		goto err;
345 	}
346 //dump_block((const char *)data, bytesRead, "rcv: ");
347 
348 	status = gBufferModule->trim(buffer, bytesRead);
349 	if (status < B_OK) {
350 		device->stats.receive.dropped++;
351 		goto err;
352 	}
353 
354 	device->stats.receive.bytes += bytesRead;
355 	device->stats.receive.packets++;
356 
357 	*_buffer = buffer;
358 	return B_OK;
359 
360 err:
361 	gBufferModule->free(buffer);
362 	return status;
363 }
364 
365 
366 status_t
367 ethernet_set_mtu(net_device *_device, size_t mtu)
368 {
369 	ethernet_device *device = (ethernet_device *)_device;
370 
371 	if (mtu > device->frame_size - ETHER_HEADER_LENGTH
372 		|| mtu <= ETHER_HEADER_LENGTH + 10)
373 		return B_BAD_VALUE;
374 
375 	device->mtu = mtu;
376 	return B_OK;
377 }
378 
379 
380 status_t
381 ethernet_set_promiscuous(net_device *_device, bool promiscuous)
382 {
383 	ethernet_device *device = (ethernet_device *)_device;
384 
385 	int32 value = (int32)promiscuous;
386 	if (ioctl(device->fd, ETHER_SETPROMISC, &value, sizeof(value)) < 0)
387 		return B_NOT_SUPPORTED;
388 
389 	return B_OK;
390 }
391 
392 
393 status_t
394 ethernet_set_media(net_device *device, uint32 media)
395 {
396 	return B_NOT_SUPPORTED;
397 }
398 
399 
400 status_t
401 ethernet_add_multicast(struct net_device *_device, const sockaddr *_address)
402 {
403 	ethernet_device *device = (ethernet_device *)_device;
404 
405 	if (_address->sa_family != AF_LINK)
406 		return B_BAD_VALUE;
407 
408 	const sockaddr_dl *address = (const sockaddr_dl *)_address;
409 	if (address->sdl_type != IFT_ETHER)
410 		return B_BAD_VALUE;
411 
412 	if (ioctl(device->fd, ETHER_ADDMULTI, LLADDR(address), 6) < 0)
413 		return errno;
414 	return B_OK;
415 }
416 
417 
418 status_t
419 ethernet_remove_multicast(struct net_device *_device, const sockaddr *_address)
420 {
421 	ethernet_device *device = (ethernet_device *)_device;
422 
423 	if (_address->sa_family != AF_LINK)
424 		return B_BAD_VALUE;
425 
426 	const sockaddr_dl *address = (const sockaddr_dl *)_address;
427 	if (address->sdl_type != IFT_ETHER)
428 		return B_BAD_VALUE;
429 
430 	if (ioctl(device->fd, ETHER_REMMULTI, LLADDR(address), 6) < 0)
431 		return errno;
432 	return B_OK;
433 }
434 
435 
436 static status_t
437 ethernet_std_ops(int32 op, ...)
438 {
439 	switch (op) {
440 		case B_MODULE_INIT:
441 		{
442 			status_t status = get_module(NET_STACK_MODULE_NAME,
443 				(module_info **)&sStackModule);
444 			if (status < B_OK)
445 				return status;
446 
447 			new (&sCheckList) DoublyLinkedList<ethernet_device>;
448 				// static C++ objects are not initialized in the module startup
449 
450 			sLinkCheckerThread = -1;
451 
452 			sLinkChangeSemaphore = create_sem(0, "ethernet link change");
453 			if (sLinkChangeSemaphore < B_OK) {
454 				put_module(NET_STACK_MODULE_NAME);
455 				return sLinkChangeSemaphore;
456 			}
457 
458 			mutex_init(&sListLock, "ethernet devices");
459 
460 			return B_OK;
461 		}
462 
463 		case B_MODULE_UNINIT:
464 		{
465 			delete_sem(sLinkChangeSemaphore);
466 
467 			status_t status;
468 			wait_for_thread(sLinkCheckerThread, &status);
469 
470 			mutex_destroy(&sListLock);
471 			put_module(NET_STACK_MODULE_NAME);
472 			return B_OK;
473 		}
474 
475 		default:
476 			return B_ERROR;
477 	}
478 }
479 
480 
481 net_device_module_info sEthernetModule = {
482 	{
483 		"network/devices/ethernet/v1",
484 		0,
485 		ethernet_std_ops
486 	},
487 	ethernet_init,
488 	ethernet_uninit,
489 	ethernet_up,
490 	ethernet_down,
491 	ethernet_control,
492 	ethernet_send_data,
493 	ethernet_receive_data,
494 	ethernet_set_mtu,
495 	ethernet_set_promiscuous,
496 	ethernet_set_media,
497 	ethernet_add_multicast,
498 	ethernet_remove_multicast,
499 };
500 
501 module_info *modules[] = {
502 	(module_info *)&sEthernetModule,
503 	NULL
504 };
505