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