1/*
2 *	ASIX AX88172/AX88772/AX88178 USB 2.0 Ethernet Driver.
3 *	Copyright (c) 2008, 2011 S.Zharski <imker@gmx.li>
4 *	Distributed under the terms of the MIT license.
5 *
6 *	Heavily based on code of the
7 *	Driver for USB Ethernet Control Model devices
8 *	Copyright (C) 2008 Michael Lotz <mmlr@mlotz.ch>
9 *	Distributed under the terms of the MIT license.
10 *
11 */
12
13
14#include "ASIXDevice.h"
15
16#include <stdio.h>
17
18#include "ASIXVendorRequests.h"
19#include "Driver.h"
20#include "Settings.h"
21
22
23// frame header used during transfer data
24struct TRXHeader {
25	uint16	fLength;
26	uint16	fInvertedLength;
27
28	TRXHeader(uint16 length = 0) { SetLength(length); }
29	bool 	IsValid() { return (fLength ^ fInvertedLength) == 0xffff; }
30	uint16	Length()  { return fLength; }
31	// TODO: low-endian convertion?
32	void	SetLength(uint16 length) {
33				fLength = length;
34				fInvertedLength = ~fLength;
35			}
36};
37
38
39ASIXDevice::ASIXDevice(usb_device device, DeviceInfo& deviceInfo)
40	:
41	fDevice(device),
42	fStatus(B_ERROR),
43	fOpen(false),
44	fRemoved(false),
45	fHasConnection(false),
46	fNonBlocking(false),
47	fInsideNotify(0),
48	fFrameSize(0),
49	fNotifyEndpoint(0),
50	fReadEndpoint(0),
51	fWriteEndpoint(0),
52	fActualLengthRead(0),
53	fActualLengthWrite(0),
54	fStatusRead(B_OK),
55	fStatusWrite(B_OK),
56	fNotifyReadSem(-1),
57	fNotifyWriteSem(-1),
58	fNotifyBuffer(NULL),
59	fNotifyBufferLength(0),
60	fLinkStateChangeSem(-1),
61	fUseTRXHeader(false),
62	fReadNodeIDRequest(kInvalidRequest)
63{
64	fDeviceInfo = deviceInfo;
65
66	fIPG[0] = 0x15;
67	fIPG[1] = 0x0c;
68	fIPG[2] = 0x12;
69
70	memset(&fMACAddress, 0, sizeof(fMACAddress));
71
72	fNotifyReadSem = create_sem(0, DRIVER_NAME"_notify_read");
73	if (fNotifyReadSem < B_OK) {
74		TRACE_ALWAYS("Error of creating read notify semaphore:%#010x\n",
75			fNotifyReadSem);
76		return;
77	}
78
79	fNotifyWriteSem = create_sem(0, DRIVER_NAME"_notify_write");
80	if (fNotifyWriteSem < B_OK) {
81		TRACE_ALWAYS("Error of creating write notify semaphore:%#010x\n",
82			fNotifyWriteSem);
83		return;
84	}
85
86	if (_SetupEndpoints() != B_OK) {
87		return;
88	}
89
90	// must be set in derived class constructor
91	// fStatus = B_OK;
92}
93
94
95ASIXDevice::~ASIXDevice()
96{
97	if (fNotifyReadSem >= B_OK)
98		delete_sem(fNotifyReadSem);
99	if (fNotifyWriteSem >= B_OK)
100		delete_sem(fNotifyWriteSem);
101
102	if (!fRemoved) // ???
103		gUSBModule->cancel_queued_transfers(fNotifyEndpoint);
104
105	if (fNotifyBuffer)
106		free(fNotifyBuffer);
107}
108
109
110status_t
111ASIXDevice::Open(uint32 flags)
112{
113	if (fOpen)
114		return B_BUSY;
115	if (fRemoved)
116		return B_ERROR;
117
118	status_t result = StartDevice();
119	if (result != B_OK) {
120		return result;
121	}
122
123	// setup state notifications
124	result = gUSBModule->queue_interrupt(fNotifyEndpoint, fNotifyBuffer,
125		fNotifyBufferLength, _NotifyCallback, this);
126	if (result != B_OK) {
127		TRACE_ALWAYS("Error of requesting notify interrupt:%#010x\n", result);
128		return result;
129	}
130
131	fNonBlocking = (flags & O_NONBLOCK) == O_NONBLOCK;
132	fOpen = true;
133	return result;
134}
135
136
137status_t
138ASIXDevice::Close()
139{
140	if (fRemoved) {
141		fOpen = false;
142		return B_OK;
143	}
144
145	// wait until possible notification handling finished...
146	while (atomic_add(&fInsideNotify, 0) != 0)
147		snooze(100);
148	gUSBModule->cancel_queued_transfers(fNotifyEndpoint);
149	gUSBModule->cancel_queued_transfers(fReadEndpoint);
150	gUSBModule->cancel_queued_transfers(fWriteEndpoint);
151
152	fOpen = false;
153
154	return StopDevice();
155}
156
157
158status_t
159ASIXDevice::Free()
160{
161	return B_OK;
162}
163
164
165status_t
166ASIXDevice::Read(uint8 *buffer, size_t *numBytes)
167{
168	size_t numBytesToRead = *numBytes;
169	*numBytes = 0;
170
171	if (fRemoved) {
172		TRACE_ALWAYS("Error of receiving %d bytes from removed device.\n",
173			numBytesToRead);
174		return B_DEVICE_NOT_FOUND;
175	}
176
177	TRACE_FLOW("Request %d bytes.\n", numBytesToRead);
178
179	TRXHeader header;
180	iovec rxData[] = {
181		{ &header, sizeof(TRXHeader) },
182		{ buffer,  numBytesToRead }
183	};
184
185	size_t startIndex = fUseTRXHeader ? 0 : 1 ;
186	size_t chunkCount = fUseTRXHeader ? 2 : 1 ;
187
188	status_t result = gUSBModule->queue_bulk_v(fReadEndpoint,
189		&rxData[startIndex], chunkCount, _ReadCallback, this);
190
191	if (result != B_OK) {
192		TRACE_ALWAYS("Error of queue_bulk_v request:%#010x\n", result);
193		return result;
194	}
195
196	uint32 flags = B_CAN_INTERRUPT | (fNonBlocking ? B_TIMEOUT : 0);
197	result = acquire_sem_etc(fNotifyReadSem, 1, flags, 0);
198	if (result < B_OK) {
199		TRACE_ALWAYS("Error of acquiring notify semaphore:%#010x.\n", result);
200		return result;
201	}
202
203	if (fStatusRead != B_OK && fStatusRead != B_CANCELED && !fRemoved) {
204		TRACE_ALWAYS("Device status error:%#010x\n", fStatusRead);
205		result = gUSBModule->clear_feature(fReadEndpoint,
206			USB_FEATURE_ENDPOINT_HALT);
207		if (result != B_OK) {
208			TRACE_ALWAYS("Error during clearing of HALT state:%#010x.\n",
209				result);
210			return result;
211		}
212	}
213
214	if (fUseTRXHeader) {
215		if (fActualLengthRead < sizeof(TRXHeader)) {
216			TRACE_ALWAYS("Error: no place for TRXHeader:only %d of %d bytes.\n",
217				fActualLengthRead, sizeof(TRXHeader));
218			return B_ERROR; // TODO: ???
219		}
220
221		if (!header.IsValid()) {
222			TRACE_ALWAYS("Error:TRX Header is invalid: len:%#04x; ilen:%#04x\n",
223				header.fLength, header.fInvertedLength);
224			return B_ERROR; // TODO: ???
225		}
226
227		*numBytes = header.Length();
228
229		// the device pushes packets 16bit aligned
230		if (fActualLengthRead - sizeof(TRXHeader) > header.Length()
231				+ (header.Length() % 2)) {
232			TRACE_ALWAYS("MISMATCH of the frame length: hdr %d; received:%d\n",
233				header.Length(), fActualLengthRead - sizeof(TRXHeader));
234		} else if (fActualLengthRead - sizeof(TRXHeader) < header.Length()) {
235			TRACE_ALWAYS("Error: received too little data: hdr %d; received:%d\n",
236				header.Length(), fActualLengthRead - sizeof(TRXHeader));
237		}
238
239	} else {
240
241		*numBytes = fActualLengthRead;
242	}
243
244	TRACE_FLOW("Read %d bytes.\n", *numBytes);
245	return B_OK;
246}
247
248
249status_t
250ASIXDevice::Write(const uint8 *buffer, size_t *numBytes)
251{
252	size_t numBytesToWrite = *numBytes;
253	*numBytes = 0;
254
255	if (fRemoved) {
256		TRACE_ALWAYS("Error of writing %d bytes to removed device.\n",
257			numBytesToWrite);
258		return B_DEVICE_NOT_FOUND;
259	}
260
261	TRACE_FLOW("Write %d bytes.\n", numBytesToWrite);
262
263	TRXHeader header(numBytesToWrite);
264	iovec txData[] = {
265		{ &header, sizeof(TRXHeader) },
266		{ (uint8*)buffer, numBytesToWrite }
267	};
268
269	size_t startIndex = fUseTRXHeader ? 0 : 1 ;
270	size_t chunkCount = fUseTRXHeader ? 2 : 1 ;
271
272	status_t result = gUSBModule->queue_bulk_v(fWriteEndpoint,
273		&txData[startIndex], chunkCount, _WriteCallback, this);
274
275	if (result != B_OK) {
276		TRACE_ALWAYS("Error of queue_bulk_v request:%#010x\n", result);
277		return result;
278	}
279
280	result = acquire_sem_etc(fNotifyWriteSem, 1, B_CAN_INTERRUPT, 0);
281
282	if (result < B_OK) {
283		TRACE_ALWAYS("Error of acquiring notify semaphore:%#010x.\n", result);
284		return result;
285	}
286
287	if (fStatusWrite != B_OK && fStatusWrite != B_CANCELED && !fRemoved) {
288		TRACE_ALWAYS("Device status error:%#010x\n", fStatusWrite);
289		result = gUSBModule->clear_feature(fWriteEndpoint,
290			USB_FEATURE_ENDPOINT_HALT);
291		if (result != B_OK) {
292			TRACE_ALWAYS("Error during clearing of HALT state:%#010x\n", result);
293			return result;
294		}
295	}
296
297	if (fUseTRXHeader) {
298		*numBytes = fActualLengthWrite - sizeof(TRXHeader);
299	} else {
300		*numBytes = fActualLengthWrite;
301	}
302
303	TRACE_FLOW("Written %d bytes.\n", *numBytes);
304	return B_OK;
305}
306
307
308status_t
309ASIXDevice::Control(uint32 op, void *buffer, size_t length)
310{
311	switch (op) {
312		case ETHER_INIT:
313			return B_OK;
314
315		case ETHER_GETADDR:
316			memcpy(buffer, &fMACAddress, sizeof(fMACAddress));
317			return B_OK;
318
319		case ETHER_GETFRAMESIZE:
320			*(uint32 *)buffer = fFrameSize;
321			return B_OK;
322
323		case ETHER_NONBLOCK:
324			TRACE("ETHER_NONBLOCK\n");
325			fNonBlocking = *((uint8*)buffer);
326			return B_OK;
327
328		case ETHER_SETPROMISC:
329			TRACE("ETHER_SETPROMISC\n");
330			return SetPromiscuousMode(*((uint8*)buffer));
331
332		case ETHER_ADDMULTI:
333			TRACE("ETHER_ADDMULTI\n");
334			return ModifyMulticastTable(true, (ether_address_t*)buffer);
335
336		case ETHER_REMMULTI:
337			TRACE("ETHER_REMMULTI\n");
338			return ModifyMulticastTable(false, (ether_address_t*)buffer);
339
340		case ETHER_SET_LINK_STATE_SEM:
341			fLinkStateChangeSem = *(sem_id *)buffer;
342			return B_OK;
343
344		case ETHER_GET_LINK_STATE:
345			return GetLinkState((ether_link_state *)buffer);
346
347		default:
348			TRACE_ALWAYS("Unhandled IOCTL catched: %#010x\n", op);
349	}
350
351	return B_DEV_INVALID_IOCTL;
352}
353
354
355void
356ASIXDevice::Removed()
357{
358	fRemoved = true;
359	fHasConnection = false;
360
361	// the notify hook is different from the read and write hooks as it does
362	// itself schedule traffic (while the other hooks only release a semaphore
363	// to notify another thread which in turn safly checks for the removed
364	// case) - so we must ensure that we are not inside the notify hook anymore
365	// before returning, as we would otherwise violate the promise not to use
366	// any of the pipes after returning from the removed hook
367	while (atomic_add(&fInsideNotify, 0) != 0)
368		snooze(100);
369
370	gUSBModule->cancel_queued_transfers(fNotifyEndpoint);
371	gUSBModule->cancel_queued_transfers(fReadEndpoint);
372	gUSBModule->cancel_queued_transfers(fWriteEndpoint);
373
374	if (fLinkStateChangeSem >= B_OK)
375		release_sem_etc(fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE);
376}
377
378
379status_t
380ASIXDevice::SetupDevice(bool deviceReplugged)
381{
382	ether_address address;
383	status_t result = ReadMACAddress(&address);
384	if (result != B_OK) {
385		TRACE_ALWAYS("Error of reading MAC address:%#010x\n", result);
386		return result;
387	}
388
389	TRACE("MAC address is:%02x:%02x:%02x:%02x:%02x:%02x\n",
390		address.ebyte[0], address.ebyte[1], address.ebyte[2],
391		address.ebyte[3], address.ebyte[4], address.ebyte[5]);
392
393	if (deviceReplugged) {
394		// this might be the same device that was replugged - read the MAC
395		// address (which should be at the same index) to make sure
396		if (memcmp(&address, &fMACAddress, sizeof(address)) != 0) {
397			TRACE_ALWAYS("Cannot replace device with MAC address:"
398				"%02x:%02x:%02x:%02x:%02x:%02x\n", fMACAddress.ebyte[0],
399				fMACAddress.ebyte[1], fMACAddress.ebyte[2],
400				fMACAddress.ebyte[3], fMACAddress.ebyte[4],
401				fMACAddress.ebyte[5]);
402			return B_BAD_VALUE; // is not the same
403		}
404	} else
405		fMACAddress = address;
406
407	return B_OK;
408}
409
410
411status_t
412ASIXDevice::CompareAndReattach(usb_device device)
413{
414	const usb_device_descriptor *deviceDescriptor
415		= gUSBModule->get_device_descriptor(device);
416
417	if (deviceDescriptor == NULL) {
418		TRACE_ALWAYS("Error of getting USB device descriptor.\n");
419		return B_ERROR;
420	}
421
422	if (deviceDescriptor->vendor_id != fDeviceInfo.VendorId()
423		&& deviceDescriptor->product_id != fDeviceInfo.ProductId()) {
424		// this certainly isn't the same device
425		return B_BAD_VALUE;
426	}
427
428	// this is the same device that was replugged - clear the removed state,
429	// re-setup the endpoints and transfers and open the device if it was
430	// previously opened
431	fDevice = device;
432	fRemoved = false;
433	status_t result = _SetupEndpoints();
434	if (result != B_OK) {
435		fRemoved = true;
436		return result;
437	}
438
439	// we need to setup hardware on device replug
440	result = SetupDevice(true);
441	if (result != B_OK) {
442		return result;
443	}
444
445	if (fOpen) {
446		fOpen = false;
447		result = Open(fNonBlocking ? O_NONBLOCK : 0);
448	}
449
450	return result;
451}
452
453
454status_t
455ASIXDevice::_SetupEndpoints()
456{
457	const usb_configuration_info *config
458		= gUSBModule->get_nth_configuration(fDevice, 0);
459
460	if (config == NULL) {
461		TRACE_ALWAYS("Error of getting USB device configuration.\n");
462		return B_ERROR;
463	}
464
465	if (config->interface_count <= 0) {
466		TRACE_ALWAYS("Error:no interfaces found in USB device configuration\n");
467		return B_ERROR;
468	}
469
470	usb_interface_info *interface = config->interface[0].active;
471	if (interface == 0) {
472		TRACE_ALWAYS("Error:invalid active interface in "
473												"USB device configuration\n");
474		return B_ERROR;
475	}
476
477	int notifyEndpoint = -1;
478	int readEndpoint   = -1;
479	int writeEndpoint  = -1;
480
481	for (size_t ep = 0; ep < interface->endpoint_count; ep++) {
482		usb_endpoint_descriptor *epd = interface->endpoint[ep].descr;
483		if ((epd->attributes & USB_ENDPOINT_ATTR_MASK)
484			== USB_ENDPOINT_ATTR_INTERRUPT) {
485			notifyEndpoint = ep;
486			continue;
487		}
488
489		if ((epd->attributes & USB_ENDPOINT_ATTR_MASK)
490			!= USB_ENDPOINT_ATTR_BULK) {
491			TRACE_ALWAYS("Error: USB endpoint type %#04x is unknown.\n",
492				epd->attributes);
493			continue;
494		}
495
496		if ((epd->endpoint_address & USB_ENDPOINT_ADDR_DIR_MASK)
497			== USB_ENDPOINT_ADDR_DIR_IN) {
498			readEndpoint = ep;
499			continue;
500		}
501
502		if ((epd->endpoint_address & USB_ENDPOINT_ADDR_DIR_MASK)
503			== USB_ENDPOINT_ADDR_DIR_OUT) {
504			writeEndpoint = ep;
505			continue;
506		}
507	}
508
509	if (notifyEndpoint == -1 || readEndpoint == -1 || writeEndpoint == -1) {
510		TRACE_ALWAYS("Error: not all USB endpoints were found: "
511			"notify:%d; read:%d; write:%d\n", notifyEndpoint, readEndpoint,
512			writeEndpoint);
513		return B_ERROR;
514	}
515
516	gUSBModule->set_configuration(fDevice, config);
517
518	fNotifyEndpoint = interface->endpoint[notifyEndpoint].handle;
519	fReadEndpoint   = interface->endpoint[readEndpoint].handle;
520	fWriteEndpoint  = interface->endpoint[writeEndpoint].handle;
521
522	return B_OK;
523}
524
525
526status_t
527ASIXDevice::ReadMACAddress(ether_address_t *address)
528{
529	size_t actual_length = 0;
530	status_t result = gUSBModule->send_request(fDevice,
531		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_IN, fReadNodeIDRequest,
532		0, 0, sizeof(ether_address), address, &actual_length);
533
534	if (result != B_OK) {
535		TRACE_ALWAYS("Error of reading MAC address:%#010x\n", result);
536		return result;
537	}
538
539	if (actual_length != sizeof(ether_address)) {
540		TRACE_ALWAYS("Mismatch of NODE ID data size: %d instead of %d bytes\n",
541										actual_length, sizeof(ether_address));
542		return B_ERROR;
543	}
544
545	return B_OK;
546}
547
548
549status_t
550ASIXDevice::ReadRXControlRegister(uint16 *rxcontrol)
551{
552	size_t actual_length = 0;
553	*rxcontrol = 0;
554
555	status_t result = gUSBModule->send_request(fDevice,
556		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_IN, READ_RX_CONTROL,
557		0, 0, sizeof(*rxcontrol), rxcontrol, &actual_length);
558
559	if (sizeof(*rxcontrol) != actual_length) {
560		TRACE_ALWAYS("Mismatch during reading RX control register."
561			"Read %d bytes instead of %d.\n", actual_length,
562			sizeof(*rxcontrol));
563	}
564
565	return result;
566}
567
568
569status_t
570ASIXDevice::WriteRXControlRegister(uint16 rxcontrol)
571{
572	status_t result = gUSBModule->send_request(fDevice,
573		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_RX_CONTROL,
574		rxcontrol, 0, 0, 0, 0);
575	return result;
576}
577
578
579status_t
580ASIXDevice::StopDevice()
581{
582	status_t result = WriteRXControlRegister(0);
583
584	if (result != B_OK)
585		TRACE_ALWAYS("Error of writing %#04x RX Control:%#010x\n", 0, result);
586
587	TRACE_RET(result);
588	return result;
589}
590
591
592status_t
593ASIXDevice::SetPromiscuousMode(bool on)
594{
595	uint16 rxcontrol = 0;
596
597	status_t result = ReadRXControlRegister(&rxcontrol);
598	if (result != B_OK) {
599		TRACE_ALWAYS("Error of reading RX Control:%#010x\n", result);
600		return result;
601	}
602
603	if (on)
604		rxcontrol |= RXCTL_PROMISCUOUS;
605	else
606		rxcontrol &= ~RXCTL_PROMISCUOUS;
607
608	result = WriteRXControlRegister(rxcontrol);
609
610	if (result != B_OK ) {
611		TRACE_ALWAYS("Error of writing %#04x RX Control:%#010x\n",
612			rxcontrol, result);
613	}
614
615	TRACE_RET(result);
616	return result;
617}
618
619
620uint32
621ASIXDevice::EthernetCRC32(const uint8* buffer, size_t length)
622{
623	uint32 result = 0xffffffff;
624	for (size_t i = 0; i < length; i++) {
625		uint8 data = *buffer++;
626		for (int bit = 0; bit < 8; bit++, data >>= 1) {
627			uint32 carry = ((result & 0x80000000) ? 1 : 0) ^ (data & 0x01);
628			result <<= 1;
629			if (carry != 0)
630				result = (result ^ 0x04c11db6) | carry;
631		}
632	}
633	return result;
634}
635
636
637status_t
638ASIXDevice::ModifyMulticastTable(bool join, ether_address_t* group)
639{
640	char groupName[6 * 3 + 1] = { 0 };
641	sprintf(groupName, "%02x:%02x:%02x:%02x:%02x:%02x",
642		group->ebyte[0], group->ebyte[1], group->ebyte[2],
643		group->ebyte[3], group->ebyte[4], group->ebyte[5]);
644	TRACE("%s multicast group %s\n", join ? "Joining" : "Leaving", groupName);
645
646	uint32 hash = EthernetCRC32(group->ebyte, 6);
647	bool isInTable = fMulticastHashes.Find(hash) != fMulticastHashes.End();
648
649	if (isInTable && join)
650		return B_OK; // already listed - nothing to do
651
652	if (!isInTable && !join) {
653		TRACE_ALWAYS("Cannot leave unlisted multicast group %s!\n", groupName);
654		return B_ERROR;
655	}
656
657	const size_t hashLength = 8;
658	uint8 hashTable[hashLength] = { 0 };
659
660	if (join)
661		fMulticastHashes.PushBack(hash);
662	else
663		fMulticastHashes.Remove(hash);
664
665	for (int32 i = 0; i < fMulticastHashes.Count(); i++) {
666		uint32 hash = fMulticastHashes[i] >> 26;
667		hashTable[hash / 8] |= 1 << (hash % 8);
668	}
669
670	uint16 rxcontrol = 0;
671
672	status_t result = ReadRXControlRegister(&rxcontrol);
673	if (result != B_OK) {
674		TRACE_ALWAYS("Error of reading RX Control:%#010x\n", result);
675		return result;
676	}
677
678	if (fMulticastHashes.Count() > 0)
679		rxcontrol |= RXCTL_MULTICAST;
680	else
681		rxcontrol &= ~RXCTL_MULTICAST;
682
683	// write multicast hash table
684	size_t actualLength = 0;
685	result = gUSBModule->send_request(fDevice,
686		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_MF_ARRAY,
687		0, 0, hashLength, hashTable, &actualLength);
688
689	if (result != B_OK) {
690		TRACE_ALWAYS("Error writing hash table in MAR: %#010x.\n", result);
691		return result;
692	}
693
694	if (actualLength != hashLength)
695		TRACE_ALWAYS("Incomplete writing of hash table: %d bytes of %d\n",
696			actualLength, hashLength);
697
698	result = WriteRXControlRegister(rxcontrol);
699	if (result != B_OK)
700		TRACE_ALWAYS("Error writing %#02X to RXC:%#010x.\n", rxcontrol, result);
701
702	return result;
703}
704
705
706void
707ASIXDevice::_ReadCallback(void *cookie, int32 status, void *data,
708	size_t actualLength)
709{
710	TRACE_FLOW("ReadCB: %d bytes; status:%#010x\n", actualLength, status);
711	ASIXDevice *device = (ASIXDevice *)cookie;
712	device->fActualLengthRead = actualLength;
713	device->fStatusRead = status;
714	release_sem_etc(device->fNotifyReadSem, 1, B_DO_NOT_RESCHEDULE);
715}
716
717
718void
719ASIXDevice::_WriteCallback(void *cookie, int32 status, void *data,
720	size_t actualLength)
721{
722	TRACE_FLOW("WriteCB: %d bytes; status:%#010x\n", actualLength, status);
723	ASIXDevice *device = (ASIXDevice *)cookie;
724	device->fActualLengthWrite = actualLength;
725	device->fStatusWrite = status;
726	release_sem_etc(device->fNotifyWriteSem, 1, B_DO_NOT_RESCHEDULE);
727}
728
729
730void
731ASIXDevice::_NotifyCallback(void *cookie, int32 status, void *data,
732	size_t actualLength)
733{
734	ASIXDevice *device = (ASIXDevice *)cookie;
735	atomic_add(&device->fInsideNotify, 1);
736	if (status == B_CANCELED || device->fRemoved) {
737		atomic_add(&device->fInsideNotify, -1);
738		return;
739	}
740
741	if (status != B_OK) {
742		TRACE_ALWAYS("Device status error:%#010x\n", status);
743		status_t result = gUSBModule->clear_feature(device->fNotifyEndpoint,
744													USB_FEATURE_ENDPOINT_HALT);
745		if (result != B_OK)
746			TRACE_ALWAYS("Error during clearing of HALT state:%#010x.\n",
747				result);
748	}
749
750	// parse data in overriden class
751	device->OnNotify(actualLength);
752
753	// schedule next notification buffer
754	gUSBModule->queue_interrupt(device->fNotifyEndpoint, device->fNotifyBuffer,
755		device->fNotifyBufferLength, _NotifyCallback, device);
756	atomic_add(&device->fInsideNotify, -1);
757}
758