1/*
2 * Copyright 2007-2009, Axel D��rfler, axeld@pinc-software.de.
3 * Copyright 2007, Hugo Santos. All Rights Reserved.
4 * Copyright 2004, Marcus Overhagen. All Rights Reserved.
5 * Distributed under the terms of the MIT License.
6 */
7
8
9#include "device.h"
10
11#include <stdlib.h>
12#include <sys/sockio.h>
13
14#include <Drivers.h>
15#include <ether_driver.h>
16
17#include <compat/sys/haiku-module.h>
18
19#include <compat/sys/bus.h>
20#include <compat/sys/mbuf.h>
21#include <compat/net/ethernet.h>
22#include <compat/net/if_media.h>
23
24
25static status_t
26compat_open(const char *name, uint32 flags, void **cookie)
27{
28	struct ifnet *ifp;
29	struct ifreq ifr;
30	int i;
31	status_t status;
32
33	for (i = 0; i < MAX_DEVICES; i++) {
34		if (gDevices[i] != NULL && !strcmp(gDevices[i]->device_name, name))
35			break;
36	}
37
38	if (i == MAX_DEVICES)
39		return B_ERROR;
40
41	if (get_module(NET_STACK_MODULE_NAME, (module_info **)&gStack) != B_OK)
42		return B_ERROR;
43
44	ifp = gDevices[i];
45	if_printf(ifp, "compat_open(0x%" B_PRIx32 ")\n", flags);
46
47	if (atomic_or(&ifp->open_count, 1)) {
48		put_module(NET_STACK_MODULE_NAME);
49		return B_BUSY;
50	}
51
52	IFF_LOCKGIANT(ifp);
53
54	if (ifp->if_init != NULL)
55		ifp->if_init(ifp->if_softc);
56
57	if (!HAIKU_DRIVER_REQUIRES(FBSD_WLAN_FEATURE)) {
58		ifp->if_flags &= ~IFF_UP;
59		ifp->if_ioctl(ifp, SIOCSIFFLAGS, NULL);
60
61		memset(&ifr, 0, sizeof(ifr));
62		ifr.ifr_media = IFM_MAKEWORD(IFM_ETHER, IFM_AUTO, 0, 0);
63		status = ifp->if_ioctl(ifp, SIOCSIFMEDIA, (caddr_t)&ifr);
64		if (status != B_OK) {
65			ifr.ifr_media = IFM_MAKEWORD(IFM_ETHER, IFM_10_T, 0, 0);
66			status = ifp->if_ioctl(ifp, SIOCSIFMEDIA, (caddr_t)&ifr);
67		}
68	}
69
70	ifp->if_flags |= IFF_UP;
71	ifp->flags &= ~DEVICE_CLOSED;
72	ifp->if_ioctl(ifp, SIOCSIFFLAGS, NULL);
73
74	IFF_UNLOCKGIANT(ifp);
75
76	*cookie = ifp;
77	return B_OK;
78}
79
80
81static status_t
82compat_close(void *cookie)
83{
84	struct ifnet *ifp = cookie;
85
86	if_printf(ifp, "compat_close()\n");
87	IFF_LOCKGIANT(ifp);
88
89	atomic_or(&ifp->flags, DEVICE_CLOSED);
90
91	wlan_close(cookie);
92
93	release_sem_etc(ifp->receive_sem, 1, B_RELEASE_ALL);
94
95	IFF_UNLOCKGIANT(ifp);
96	return B_OK;
97}
98
99
100static status_t
101compat_free(void *cookie)
102{
103	struct ifnet *ifp = cookie;
104
105	if_printf(ifp, "compat_free()\n");
106
107	// TODO: empty out the send queue
108
109	atomic_and(&ifp->open_count, 0);
110	put_module(NET_STACK_MODULE_NAME);
111	return B_OK;
112}
113
114
115static status_t
116compat_read(void *cookie, off_t position, void *buffer, size_t *numBytes)
117{
118	struct ifnet *ifp = cookie;
119	uint32 semFlags = B_CAN_INTERRUPT;
120	status_t status;
121	struct mbuf *mb;
122	size_t length = *numBytes;
123
124	//if_printf(ifp, "compat_read(%lld, %p, [%lu])\n", position,
125	//	buffer, *numBytes);
126
127	if (ifp->flags & DEVICE_CLOSED)
128		return B_INTERRUPTED;
129
130	if (ifp->flags & DEVICE_NON_BLOCK)
131		semFlags |= B_RELATIVE_TIMEOUT;
132
133	do {
134		status = acquire_sem_etc(ifp->receive_sem, 1, semFlags, 0);
135		if (ifp->flags & DEVICE_CLOSED)
136			return B_INTERRUPTED;
137
138		if (status == B_WOULD_BLOCK) {
139			*numBytes = 0;
140			return B_OK;
141		} else if (status < B_OK)
142			return status;
143
144		IF_DEQUEUE(&ifp->receive_queue, mb);
145	} while (mb == NULL);
146
147	if (mb->m_pkthdr.len > length) {
148		if_printf(ifp, "error reading packet: too large! (%d > %" B_PRIuSIZE ")\n",
149			mb->m_pkthdr.len, length);
150		m_freem(mb);
151		*numBytes = 0;
152		return E2BIG;
153	}
154
155	length = min_c(max_c((size_t)mb->m_pkthdr.len, 0), length);
156
157	m_copydata(mb, 0, length, buffer);
158	*numBytes = length;
159
160	m_freem(mb);
161	return B_OK;
162}
163
164
165static status_t
166compat_write(void *cookie, off_t position, const void *buffer,
167	size_t *numBytes)
168{
169	struct ifnet *ifp = cookie;
170	struct mbuf *mb;
171	int length = *numBytes;
172
173	//if_printf(ifp, "compat_write(%lld, %p, [%lu])\n", position,
174	//	buffer, *numBytes);
175
176	if (length <= MHLEN) {
177		mb = m_gethdr(0, MT_DATA);
178		if (mb == NULL)
179			return ENOBUFS;
180	} else {
181		mb = m_get2(length, 0, MT_DATA, M_PKTHDR);
182		if (mb == NULL)
183			return E2BIG;
184
185		length = min_c(length, mb->m_ext.ext_size);
186	}
187
188	// if we waited, check after if the ifp is still valid
189
190	mb->m_pkthdr.len = mb->m_len = length;
191	memcpy(mtod(mb, void *), buffer, mb->m_len);
192	*numBytes = length;
193
194	IFF_LOCKGIANT(ifp);
195	int result = ifp->if_output(ifp, mb, NULL, NULL);
196	IFF_UNLOCKGIANT(ifp);
197
198	return result;
199}
200
201
202static status_t
203compat_control(void *cookie, uint32 op, void *arg, size_t length)
204{
205	struct ifnet *ifp = cookie;
206	status_t status;
207
208	//if_printf(ifp, "compat_control(op %lu, %p, [%lu])\n", op,
209	//	arg, length);
210
211	switch (op) {
212		case ETHER_INIT:
213			return B_OK;
214
215		case ETHER_GETADDR:
216			return user_memcpy(arg, IF_LLADDR(ifp), ETHER_ADDR_LEN);
217
218		case ETHER_NONBLOCK:
219		{
220			int32 value;
221			if (length < 4)
222				return B_BAD_VALUE;
223			if (user_memcpy(&value, arg, sizeof(int32)) < B_OK)
224				return B_BAD_ADDRESS;
225			if (value)
226				ifp->flags |= DEVICE_NON_BLOCK;
227			else
228				ifp->flags &= ~DEVICE_NON_BLOCK;
229			return B_OK;
230		}
231
232		case ETHER_SETPROMISC:
233		{
234			int32 value;
235			if (length < 4)
236				return B_BAD_VALUE;
237			if (user_memcpy(&value, arg, sizeof(int32)) < B_OK)
238				return B_BAD_ADDRESS;
239			if (value)
240				ifp->if_flags |= IFF_PROMISC;
241			else
242				ifp->if_flags &= ~IFF_PROMISC;
243
244			IFF_LOCKGIANT(ifp);
245			status = ifp->if_ioctl(ifp, SIOCSIFFLAGS, NULL);
246			IFF_UNLOCKGIANT(ifp);
247			return status;
248		}
249
250		case ETHER_GETFRAMESIZE:
251		{
252			uint32 frameSize;
253			if (length < 4)
254				return B_BAD_VALUE;
255
256			const int MTUs[] = {
257				ETHERMTU_JUMBO,
258				PAGESIZE - (ETHER_HDR_LEN + ETHER_CRC_LEN),
259				2290, /* IEEE80211_MTU_MAX */
260				0
261			};
262
263			// This is (usually) only invoked during initialization to get the
264			// maximum frame size. Thus we try a few common possible values,
265			// as there is no way to determine what is supported (or required).
266			for (int i = 0; MTUs[i] != 0; i++) {
267				struct ifreq ifr;
268				ifr.ifr_mtu = MTUs[i];
269				if (compat_control(cookie, SIOCSIFMTU, &ifr, sizeof(ifr)) == 0)
270					break;
271			}
272
273			frameSize = ifp->if_mtu + (ETHER_HDR_LEN + ETHER_CRC_LEN);
274			return user_memcpy(arg, &frameSize, 4);
275		}
276
277		case ETHER_ADDMULTI:
278		case ETHER_REMMULTI:
279		{
280			struct sockaddr_dl address;
281
282			if ((ifp->if_flags & IFF_MULTICAST) == 0)
283				return B_NOT_SUPPORTED;
284			if (length != ETHER_ADDR_LEN)
285				return B_BAD_VALUE;
286
287			memset(&address, 0, sizeof(address));
288			address.sdl_family = AF_LINK;
289			if (user_memcpy(LLADDR(&address), arg, ETHER_ADDR_LEN) < B_OK)
290				return B_BAD_ADDRESS;
291
292			IFF_LOCKGIANT(ifp);
293			if (op == ETHER_ADDMULTI)
294				status = if_addmulti(ifp, (struct sockaddr *)&address, NULL);
295			else
296				status = if_delmulti(ifp, (struct sockaddr *)&address);
297			IFF_UNLOCKGIANT(ifp);
298			return status;
299		}
300
301		case ETHER_GET_LINK_STATE:
302		{
303			struct ifmediareq mediareq;
304			ether_link_state_t state;
305
306			if (length < sizeof(ether_link_state_t))
307				return EINVAL;
308
309			memset(&mediareq, 0, sizeof(mediareq));
310			IFF_LOCKGIANT(ifp);
311			status = ifp->if_ioctl(ifp, SIOCGIFMEDIA, (caddr_t)&mediareq);
312			IFF_UNLOCKGIANT(ifp);
313			if (status < B_OK)
314				return status;
315
316			state.media = mediareq.ifm_active;
317			if ((mediareq.ifm_status & IFM_ACTIVE) != 0)
318				state.media |= IFM_ACTIVE;
319			state.speed = ifmedia_baudrate(mediareq.ifm_active);
320			state.quality = 1000;
321
322			return user_memcpy(arg, &state, sizeof(ether_link_state_t));
323		}
324
325		case ETHER_SET_LINK_STATE_SEM:
326			if (user_memcpy(&ifp->link_state_sem, arg, sizeof(sem_id)) < B_OK) {
327				ifp->link_state_sem = -1;
328				return B_BAD_ADDRESS;
329			}
330			return B_OK;
331
332		case SIOCSIFFLAGS:
333		case SIOCSIFMEDIA:
334		case SIOCSIFMTU:
335		{
336			IFF_LOCKGIANT(ifp);
337			status = ifp->if_ioctl(ifp, op, (caddr_t)arg);
338			IFF_UNLOCKGIANT(ifp);
339			return status;
340		}
341	}
342
343	return wlan_control(cookie, op, arg, length);
344}
345
346
347device_hooks gDeviceHooks = {
348	compat_open,
349	compat_close,
350	compat_free,
351	compat_control,
352	compat_read,
353	compat_write,
354};
355