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