1/*
2	Driver for USB Ethernet Control Model devices
3	Copyright (C) 2008 Michael Lotz <mmlr@mlotz.ch>
4	Distributed under the terms of the MIT license.
5*/
6#include <ether_driver.h>
7#include <net/if_media.h>
8#include <string.h>
9#include <stdlib.h>
10
11#include "BeOSCompatibility.h"
12#include "ECMDevice.h"
13#include "Driver.h"
14
15ECMDevice::ECMDevice(usb_device device)
16	:	fStatus(B_ERROR),
17		fOpen(false),
18		fRemoved(false),
19		fInsideNotify(0),
20		fDevice(device),
21		fControlInterfaceIndex(0),
22		fDataInterfaceIndex(0),
23		fMACAddressIndex(0),
24		fMaxSegmentSize(0),
25		fNotifyEndpoint(0),
26		fReadEndpoint(0),
27		fWriteEndpoint(0),
28		fNotifyReadSem(-1),
29		fNotifyWriteSem(-1),
30		fNotifyBuffer(NULL),
31		fNotifyBufferLength(0),
32		fLinkStateChangeSem(-1),
33		fHasConnection(false),
34		fDownstreamSpeed(0),
35		fUpstreamSpeed(0)
36{
37	const usb_device_descriptor *deviceDescriptor
38		= gUSBModule->get_device_descriptor(device);
39
40	if (deviceDescriptor == NULL) {
41		TRACE_ALWAYS("failed to get device descriptor\n");
42		return;
43	}
44
45	fVendorID = deviceDescriptor->vendor_id;
46	fProductID = deviceDescriptor->product_id;
47
48	fNotifyBufferLength = 64;
49	fNotifyBuffer = (uint8 *)malloc(fNotifyBufferLength);
50	if (fNotifyBuffer == NULL) {
51		TRACE_ALWAYS("out of memory for notify buffer allocation\n");
52		return;
53	}
54
55	fNotifyReadSem = create_sem(0, DRIVER_NAME"_notify_read");
56	if (fNotifyReadSem < B_OK) {
57		TRACE_ALWAYS("failed to create read notify sem\n");
58		return;
59	}
60
61	fNotifyWriteSem = create_sem(0, DRIVER_NAME"_notify_write");
62	if (fNotifyWriteSem < B_OK) {
63		TRACE_ALWAYS("failed to create write notify sem\n");
64		return;
65	}
66
67	if (_SetupDevice() != B_OK) {
68		TRACE_ALWAYS("failed to setup device\n");
69		return;
70	}
71
72	if (_ReadMACAddress(fDevice, fMACAddress) != B_OK) {
73		TRACE_ALWAYS("failed to read mac address\n");
74		return;
75	}
76
77	fStatus = B_OK;
78}
79
80
81ECMDevice::~ECMDevice()
82{
83	if (fNotifyReadSem >= B_OK)
84		delete_sem(fNotifyReadSem);
85	if (fNotifyWriteSem >= B_OK)
86		delete_sem(fNotifyWriteSem);
87
88	if (!fRemoved)
89		gUSBModule->cancel_queued_transfers(fNotifyEndpoint);
90
91	free(fNotifyBuffer);
92}
93
94
95status_t
96ECMDevice::Open()
97{
98	if (fOpen)
99		return B_BUSY;
100	if (fRemoved)
101		return B_ERROR;
102
103	// reset the device by switching the data interface to the disabled first
104	// interface and then enable it by setting the second actual data interface
105	const usb_configuration_info *config
106		= gUSBModule->get_nth_configuration(fDevice, 0);
107
108	gUSBModule->set_alt_interface(fDevice,
109		&config->interface[fDataInterfaceIndex].alt[0]);
110
111	// update to the changed config
112	config = gUSBModule->get_nth_configuration(fDevice, 0);
113	gUSBModule->set_alt_interface(fDevice,
114		&config->interface[fDataInterfaceIndex].alt[1]);
115
116	// update again
117	config = gUSBModule->get_nth_configuration(fDevice, 0);
118	usb_interface_info *interface = config->interface[fDataInterfaceIndex].active;
119	if (interface->endpoint_count < 2) {
120		TRACE_ALWAYS("setting the data alternate interface failed\n");
121		return B_ERROR;
122	}
123
124	if (!(interface->endpoint[0].descr->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN))
125		fWriteEndpoint = interface->endpoint[0].handle;
126	else
127		fReadEndpoint = interface->endpoint[0].handle;
128
129	if (interface->endpoint[1].descr->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN)
130		fReadEndpoint = interface->endpoint[1].handle;
131	else
132		fWriteEndpoint = interface->endpoint[1].handle;
133
134	if (fReadEndpoint == 0 || fWriteEndpoint == 0) {
135		TRACE_ALWAYS("no read and write endpoints found\n");
136		return B_ERROR;
137	}
138
139	// the device should now be ready
140	fOpen = true;
141	return B_OK;
142}
143
144
145status_t
146ECMDevice::Close()
147{
148	if (fRemoved) {
149		fOpen = false;
150		return B_OK;
151	}
152
153	gUSBModule->cancel_queued_transfers(fReadEndpoint);
154	gUSBModule->cancel_queued_transfers(fWriteEndpoint);
155
156	// put the device into non-connected mode again by switching the data
157	// interface to the disabled alternate
158	const usb_configuration_info *config
159		= gUSBModule->get_nth_configuration(fDevice, 0);
160
161	gUSBModule->set_alt_interface(fDevice,
162		&config->interface[fDataInterfaceIndex].alt[0]);
163
164	fOpen = false;
165	return B_OK;
166}
167
168
169status_t
170ECMDevice::Free()
171{
172	return B_OK;
173}
174
175
176status_t
177ECMDevice::Read(uint8 *buffer, size_t *numBytes)
178{
179	if (fRemoved) {
180		*numBytes = 0;
181		return B_DEVICE_NOT_FOUND;
182	}
183
184	status_t result = gUSBModule->queue_bulk(fReadEndpoint, buffer, *numBytes,
185		_ReadCallback, this);
186	if (result != B_OK) {
187		*numBytes = 0;
188		return result;
189	}
190
191	result = acquire_sem_etc(fNotifyReadSem, 1, B_CAN_INTERRUPT, 0);
192	if (result < B_OK) {
193		*numBytes = 0;
194		return result;
195	}
196
197	if (fStatusRead != B_OK && fStatusRead != B_CANCELED && !fRemoved) {
198		TRACE_ALWAYS("device status error 0x%08lx\n", fStatusRead);
199		result = gUSBModule->clear_feature(fReadEndpoint,
200			USB_FEATURE_ENDPOINT_HALT);
201		if (result != B_OK) {
202			TRACE_ALWAYS("failed to clear halt state on read\n");
203			*numBytes = 0;
204			return result;
205		}
206	}
207
208	*numBytes = fActualLengthRead;
209	return B_OK;
210}
211
212
213status_t
214ECMDevice::Write(const uint8 *buffer, size_t *numBytes)
215{
216	if (fRemoved) {
217		*numBytes = 0;
218		return B_DEVICE_NOT_FOUND;
219	}
220
221	status_t result = gUSBModule->queue_bulk(fWriteEndpoint, (uint8 *)buffer,
222		*numBytes, _WriteCallback, this);
223	if (result != B_OK) {
224		*numBytes = 0;
225		return result;
226	}
227
228	result = acquire_sem_etc(fNotifyWriteSem, 1, B_CAN_INTERRUPT, 0);
229	if (result < B_OK) {
230		*numBytes = 0;
231		return result;
232	}
233
234	if (fStatusWrite != B_OK && fStatusWrite != B_CANCELED && !fRemoved) {
235		TRACE_ALWAYS("device status error 0x%08lx\n", fStatusWrite);
236		result = gUSBModule->clear_feature(fWriteEndpoint,
237			USB_FEATURE_ENDPOINT_HALT);
238		if (result != B_OK) {
239			TRACE_ALWAYS("failed to clear halt state on write\n");
240			*numBytes = 0;
241			return result;
242		}
243	}
244
245	*numBytes = fActualLengthWrite;
246	return B_OK;
247}
248
249
250status_t
251ECMDevice::Control(uint32 op, void *buffer, size_t length)
252{
253	switch (op) {
254		case ETHER_INIT:
255			return B_OK;
256
257		case ETHER_GETADDR:
258			memcpy(buffer, &fMACAddress, sizeof(fMACAddress));
259			return B_OK;
260
261		case ETHER_GETFRAMESIZE:
262			*(uint32 *)buffer = fMaxSegmentSize;
263			return B_OK;
264
265#if HAIKU_TARGET_PLATFORM_HAIKU
266		case ETHER_SET_LINK_STATE_SEM:
267			fLinkStateChangeSem = *(sem_id *)buffer;
268			return B_OK;
269
270		case ETHER_GET_LINK_STATE:
271		{
272			ether_link_state *state = (ether_link_state *)buffer;
273			state->media = IFM_ETHER | IFM_FULL_DUPLEX
274				| (fHasConnection ? IFM_ACTIVE : 0);
275			state->quality = 1000;
276			state->speed = fDownstreamSpeed;
277			return B_OK;
278		}
279#endif
280
281		default:
282			TRACE_ALWAYS("unsupported ioctl %lu\n", op);
283	}
284
285	return B_DEV_INVALID_IOCTL;
286}
287
288
289void
290ECMDevice::Removed()
291{
292	fRemoved = true;
293	fHasConnection = false;
294	fDownstreamSpeed = fUpstreamSpeed = 0;
295
296	// the notify hook is different from the read and write hooks as it does
297	// itself schedule traffic (while the other hooks only release a semaphore
298	// to notify another thread which in turn safly checks for the removed
299	// case) - so we must ensure that we are not inside the notify hook anymore
300	// before returning, as we would otherwise violate the promise not to use
301	// any of the pipes after returning from the removed hook
302	while (atomic_add(&fInsideNotify, 0) != 0)
303		snooze(100);
304
305	gUSBModule->cancel_queued_transfers(fNotifyEndpoint);
306	gUSBModule->cancel_queued_transfers(fReadEndpoint);
307	gUSBModule->cancel_queued_transfers(fWriteEndpoint);
308
309	if (fLinkStateChangeSem >= B_OK)
310		release_sem_etc(fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE);
311}
312
313
314status_t
315ECMDevice::CompareAndReattach(usb_device device)
316{
317	const usb_device_descriptor *deviceDescriptor
318		= gUSBModule->get_device_descriptor(device);
319
320	if (deviceDescriptor == NULL) {
321		TRACE_ALWAYS("failed to get device descriptor\n");
322		return B_ERROR;
323	}
324
325	if (deviceDescriptor->vendor_id != fVendorID
326		&& deviceDescriptor->product_id != fProductID) {
327		// this certainly isn't the same device
328		return B_BAD_VALUE;
329	}
330
331	// this might be the same device that was replugged - read the MAC address
332	// (which should be at the same index) to make sure
333	uint8 macBuffer[6];
334	if (_ReadMACAddress(device, macBuffer) != B_OK
335		|| memcmp(macBuffer, fMACAddress, sizeof(macBuffer)) != 0) {
336		// reading the MAC address failed or they are not the same
337		return B_BAD_VALUE;
338	}
339
340	// this is the same device that was replugged - clear the removed state,
341	// re-setup the endpoints and transfers and open the device if it was
342	// previously opened
343	fDevice = device;
344	fRemoved = false;
345	status_t result = _SetupDevice();
346	if (result != B_OK) {
347		fRemoved = true;
348		return result;
349	}
350
351	// in case notifications do not work we will have a hardcoded connection
352	// need to register that and notify the network stack ourselfs if this is
353	// the case as the open will not result in a corresponding notification
354	bool noNotifications = fHasConnection;
355
356	if (fOpen) {
357		fOpen = false;
358		result = Open();
359		if (result == B_OK && noNotifications && fLinkStateChangeSem >= B_OK)
360			release_sem_etc(fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE);
361	}
362
363	return B_OK;
364}
365
366
367status_t
368ECMDevice::_SetupDevice()
369{
370	const usb_configuration_info *config
371		= gUSBModule->get_nth_configuration(fDevice, 0);
372
373	if (config == NULL) {
374		TRACE_ALWAYS("failed to get device configuration\n");
375		return B_ERROR;
376	}
377
378	uint8 controlIndex = 0;
379	uint8 dataIndex = 0;
380	bool foundUnionDescriptor = false;
381	bool foundEthernetDescriptor = false;
382	for (size_t i = 0; i < config->interface_count
383		&& (!foundUnionDescriptor || !foundEthernetDescriptor); i++) {
384		usb_interface_info *interface = config->interface[i].active;
385		usb_interface_descriptor *descriptor = interface->descr;
386		if (descriptor->interface_class == USB_INTERFACE_CLASS_CDC
387			&& descriptor->interface_subclass == USB_INTERFACE_SUBCLASS_ECM
388			&& interface->generic_count > 0) {
389			// try to find and interpret the union and ethernet functional
390			// descriptors
391			foundUnionDescriptor = foundEthernetDescriptor = false;
392			for (size_t j = 0; j < interface->generic_count; j++) {
393				usb_generic_descriptor *generic = &interface->generic[j]->generic;
394				if (generic->length >= 5
395					&& generic->data[0] == FUNCTIONAL_SUBTYPE_UNION) {
396					controlIndex = generic->data[1];
397					dataIndex = generic->data[2];
398					foundUnionDescriptor = true;
399				} else if (generic->length >= sizeof(ethernet_functional_descriptor)
400					&& generic->data[0] == FUNCTIONAL_SUBTYPE_ETHERNET) {
401					ethernet_functional_descriptor *ethernet
402						= (ethernet_functional_descriptor *)generic->data;
403					fMACAddressIndex = ethernet->mac_address_index;
404					fMaxSegmentSize = ethernet->max_segment_size;
405					foundEthernetDescriptor = true;
406				}
407
408				if (foundUnionDescriptor && foundEthernetDescriptor)
409					break;
410			}
411		}
412	}
413
414	if (!foundUnionDescriptor) {
415		TRACE_ALWAYS("did not find a union descriptor\n");
416		return B_ERROR;
417	}
418
419	if (!foundEthernetDescriptor) {
420		TRACE_ALWAYS("did not find an ethernet descriptor\n");
421		return B_ERROR;
422	}
423
424	if (controlIndex >= config->interface_count) {
425		TRACE_ALWAYS("control interface index invalid\n");
426		return B_ERROR;
427	}
428
429	// check that the indicated control interface fits our needs
430	usb_interface_info *interface = config->interface[controlIndex].active;
431	usb_interface_descriptor *descriptor = interface->descr;
432	if ((descriptor->interface_class != USB_INTERFACE_CLASS_CDC
433		|| descriptor->interface_subclass != USB_INTERFACE_SUBCLASS_ECM)
434		|| interface->endpoint_count == 0) {
435		TRACE_ALWAYS("control interface invalid\n");
436		return B_ERROR;
437	}
438
439	fControlInterfaceIndex = controlIndex;
440	fNotifyEndpoint = interface->endpoint[0].handle;
441	if (gUSBModule->queue_interrupt(fNotifyEndpoint, fNotifyBuffer,
442		fNotifyBufferLength, _NotifyCallback, this) != B_OK) {
443		// we cannot use notifications - hardcode to active connection
444		fHasConnection = true;
445		fDownstreamSpeed = 1000 * 1000 * 10; // 10Mbps
446		fUpstreamSpeed = 1000 * 1000 * 10; // 10Mbps
447	}
448
449	if (dataIndex >= config->interface_count) {
450		TRACE_ALWAYS("data interface index invalid\n");
451		return B_ERROR;
452	}
453
454	// check that the indicated data interface fits our needs
455	if (config->interface[dataIndex].alt_count < 2) {
456		TRACE_ALWAYS("data interface does not provide two alternate interfaces\n");
457		return B_ERROR;
458	}
459
460	// alternate 0 is the disabled, endpoint-less default interface
461	interface = &config->interface[dataIndex].alt[1];
462	descriptor = interface->descr;
463	if (descriptor->interface_class != USB_INTERFACE_CLASS_CDC_DATA
464		|| interface->endpoint_count < 2) {
465		TRACE_ALWAYS("data interface invalid\n");
466		return B_ERROR;
467	}
468
469	fDataInterfaceIndex = dataIndex;
470	return B_OK;
471}
472
473
474status_t
475ECMDevice::_ReadMACAddress(usb_device device, uint8 *buffer)
476{
477	if (fMACAddressIndex == 0)
478		return B_BAD_VALUE;
479
480	size_t actualLength = 0;
481	size_t macStringLength = 26;
482	uint8 macString[macStringLength];
483	status_t result = gUSBModule->get_descriptor(device, USB_DESCRIPTOR_STRING,
484		fMACAddressIndex, 0, macString, macStringLength, &actualLength);
485	if (result != B_OK)
486		return result;
487
488	if (actualLength != macStringLength) {
489		TRACE_ALWAYS("did not retrieve full mac address\n");
490		return B_ERROR;
491	}
492
493	char macPart[3];
494	macPart[2] = 0;
495	for (int32 i = 0; i < 6; i++) {
496		macPart[0] = macString[2 + i * 4 + 0];
497		macPart[1] = macString[2 + i * 4 + 2];
498		buffer[i] = strtol(macPart, NULL, 16);
499	}
500
501	TRACE_ALWAYS("read mac address: %02x:%02x:%02x:%02x:%02x:%02x\n",
502		buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]);
503	return B_OK;
504}
505
506
507void
508ECMDevice::_ReadCallback(void *cookie, int32 status, void *data,
509	uint32 actualLength)
510{
511	ECMDevice *device = (ECMDevice *)cookie;
512	device->fActualLengthRead = actualLength;
513	device->fStatusRead = status;
514	release_sem_etc(device->fNotifyReadSem, 1, B_DO_NOT_RESCHEDULE);
515}
516
517
518void
519ECMDevice::_WriteCallback(void *cookie, int32 status, void *data,
520	uint32 actualLength)
521{
522	ECMDevice *device = (ECMDevice *)cookie;
523	device->fActualLengthWrite = actualLength;
524	device->fStatusWrite = status;
525	release_sem_etc(device->fNotifyWriteSem, 1, B_DO_NOT_RESCHEDULE);
526}
527
528
529void
530ECMDevice::_NotifyCallback(void *cookie, int32 status, void *data,
531	uint32 actualLength)
532{
533	ECMDevice *device = (ECMDevice *)cookie;
534	atomic_add(&device->fInsideNotify, 1);
535	if (status == B_CANCELED || device->fRemoved) {
536		atomic_add(&device->fInsideNotify, -1);
537		return;
538	}
539
540	if (status == B_OK && actualLength >= sizeof(cdc_notification)) {
541		bool linkStateChange = false;
542		cdc_notification *notification
543			= (cdc_notification *)device->fNotifyBuffer;
544
545		switch (notification->notification_code) {
546			case CDC_NOTIFY_NETWORK_CONNECTION:
547				TRACE("connection state change to %d\n", notification->value);
548				device->fHasConnection = notification->value > 0;
549				linkStateChange = true;
550				break;
551
552			case CDC_NOTIFY_CONNECTION_SPEED_CHANGE:
553			{
554				if (notification->data_length < sizeof(cdc_connection_speed)
555					|| actualLength < sizeof(cdc_notification)
556					+ sizeof(cdc_connection_speed)) {
557					TRACE_ALWAYS("not enough data in connection speed change\n");
558					break;
559				}
560
561				cdc_connection_speed *speed;
562				speed = (cdc_connection_speed *)&notification->data[0];
563				device->fUpstreamSpeed = speed->upstream_speed;
564				device->fDownstreamSpeed = speed->downstream_speed;
565				device->fHasConnection = true;
566				TRACE("connection speed change to %ld/%ld\n",
567					speed->downstream_speed, speed->upstream_speed);
568				linkStateChange = true;
569				break;
570			}
571
572			default:
573				TRACE_ALWAYS("unsupported notification 0x%02x\n",
574					notification->notification_code);
575				break;
576		}
577
578		if (linkStateChange && device->fLinkStateChangeSem >= B_OK)
579			release_sem_etc(device->fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE);
580	}
581
582	if (status != B_OK) {
583		TRACE_ALWAYS("device status error 0x%08lx\n", status);
584		if (gUSBModule->clear_feature(device->fNotifyEndpoint,
585			USB_FEATURE_ENDPOINT_HALT) != B_OK)
586			TRACE_ALWAYS("failed to clear halt state in notify hook\n");
587	}
588
589	// schedule next notification buffer
590	gUSBModule->queue_interrupt(device->fNotifyEndpoint, device->fNotifyBuffer,
591		device->fNotifyBufferLength, _NotifyCallback, device);
592	atomic_add(&device->fInsideNotify, -1);
593}
594