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
33struct ethernet_device : net_device, DoublyLinkedListLinkImpl<ethernet_device> {
34	int		fd;
35	uint32	frame_size;
36};
37
38static const bigtime_t kLinkCheckInterval = 1000000;
39	// 1 second
40
41net_buffer_module_info *gBufferModule;
42static net_stack_module_info *sStackModule;
43
44static mutex sListLock;
45static DoublyLinkedList<ethernet_device> sCheckList;
46static sem_id sLinkChangeSemaphore;
47static thread_id sLinkCheckerThread;
48
49
50static status_t
51update_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			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
88static status_t
89ethernet_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
118status_t
119ethernet_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)
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
152status_t
153ethernet_uninit(net_device *device)
154{
155	put_module(NET_BUFFER_MODULE_NAME);
156	delete device;
157
158	return B_OK;
159}
160
161
162status_t
163ethernet_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
208err:
209	close(device->fd);
210	device->fd = -1;
211	return errno;
212}
213
214
215void
216ethernet_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
233status_t
234ethernet_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
242status_t
243ethernet_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 errno;
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
294status_t
295ethernet_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 = errno;
327		goto err;
328	}
329//dump_block((const char *)data, bytesRead, "rcv: ");
330
331	status = gBufferModule->trim(buffer, bytesRead);
332	if (status < B_OK) {
333		device->stats.receive.dropped++;
334		goto err;
335	}
336
337	device->stats.receive.bytes += bytesRead;
338	device->stats.receive.packets++;
339
340	*_buffer = buffer;
341	return B_OK;
342
343err:
344	gBufferModule->free(buffer);
345	return status;
346}
347
348
349status_t
350ethernet_set_mtu(net_device *_device, size_t mtu)
351{
352	ethernet_device *device = (ethernet_device *)_device;
353
354	if (mtu > device->frame_size - ETHER_HEADER_LENGTH
355		|| mtu <= ETHER_HEADER_LENGTH + 10)
356		return B_BAD_VALUE;
357
358	device->mtu = mtu;
359	return B_OK;
360}
361
362
363status_t
364ethernet_set_promiscuous(net_device *_device, bool promiscuous)
365{
366	ethernet_device *device = (ethernet_device *)_device;
367
368	int32 value = (int32)promiscuous;
369	if (ioctl(device->fd, ETHER_SETPROMISC, &value, sizeof(value)) < 0)
370		return B_NOT_SUPPORTED;
371
372	return B_OK;
373}
374
375
376status_t
377ethernet_set_media(net_device *device, uint32 media)
378{
379	return B_NOT_SUPPORTED;
380}
381
382
383status_t
384ethernet_add_multicast(struct net_device *_device, const sockaddr *_address)
385{
386	ethernet_device *device = (ethernet_device *)_device;
387
388	if (_address->sa_family != AF_LINK)
389		return B_BAD_VALUE;
390
391	const sockaddr_dl *address = (const sockaddr_dl *)_address;
392	if (address->sdl_type != IFT_ETHER)
393		return B_BAD_VALUE;
394
395	return ioctl(device->fd, ETHER_ADDMULTI, LLADDR(address), 6);
396}
397
398
399status_t
400ethernet_remove_multicast(struct net_device *_device, const sockaddr *_address)
401{
402	ethernet_device *device = (ethernet_device *)_device;
403
404	if (_address->sa_family != AF_LINK)
405		return B_BAD_VALUE;
406
407	const sockaddr_dl *address = (const sockaddr_dl *)_address;
408	if (address->sdl_type != IFT_ETHER)
409		return B_BAD_VALUE;
410
411	return ioctl(device->fd, ETHER_REMMULTI, LLADDR(address), 6);
412}
413
414
415static status_t
416ethernet_std_ops(int32 op, ...)
417{
418	switch (op) {
419		case B_MODULE_INIT:
420		{
421			status_t status = get_module(NET_STACK_MODULE_NAME,
422				(module_info **)&sStackModule);
423			if (status < B_OK)
424				return status;
425
426			new (&sCheckList) DoublyLinkedList<ethernet_device>;
427				// static C++ objects are not initialized in the module startup
428
429			sLinkCheckerThread = -1;
430
431			sLinkChangeSemaphore = create_sem(0, "ethernet link change");
432			if (sLinkChangeSemaphore < B_OK) {
433				put_module(NET_STACK_MODULE_NAME);
434				return sLinkChangeSemaphore;
435			}
436
437			mutex_init(&sListLock, "ethernet devices");
438
439			return B_OK;
440		}
441
442		case B_MODULE_UNINIT:
443		{
444			delete_sem(sLinkChangeSemaphore);
445
446			status_t status;
447			wait_for_thread(sLinkCheckerThread, &status);
448
449			mutex_destroy(&sListLock);
450			put_module(NET_STACK_MODULE_NAME);
451			return B_OK;
452		}
453
454		default:
455			return B_ERROR;
456	}
457}
458
459
460net_device_module_info sEthernetModule = {
461	{
462		"network/devices/ethernet/v1",
463		0,
464		ethernet_std_ops
465	},
466	ethernet_init,
467	ethernet_uninit,
468	ethernet_up,
469	ethernet_down,
470	ethernet_control,
471	ethernet_send_data,
472	ethernet_receive_data,
473	ethernet_set_mtu,
474	ethernet_set_promiscuous,
475	ethernet_set_media,
476	ethernet_add_multicast,
477	ethernet_remove_multicast,
478};
479
480module_info *modules[] = {
481	(module_info *)&sEthernetModule,
482	NULL
483};
484