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