/*
 *	Davicom DM9601 USB 1.1 Ethernet Driver.
 *	Copyright (c) 2008, 2011 Siarzhuk Zharski <imker@gmx.li>
 *	Copyright (c) 2009 Adrien Destugues <pulkomandy@gmail.com>
 *	Distributed under the terms of the MIT license.
 *
 *	Heavily based on code of the
 *	Driver for USB Ethernet Control Model devices
 *	Copyright (C) 2008 Michael Lotz <mmlr@mlotz.ch>
 *	Distributed under the terms of the MIT license.
 */
 
 
#include "DavicomDevice.h"
 
#include <stdio.h>
#include <net/if_media.h>
 
#include "Driver.h"
#include "Settings.h"
 
 
const int kFrameSize = 1522;
 
enum VendorRequests {
	ReqReadRegister			= 0,
	ReqWriteRegister		= 1,
	ReqWriteRegisterByte	= 3,
};
 
 
enum DM9601Registers {
	RegNCR	= 0x00,	// Network Control Register
		NCRExtPHY	= 0x80,	// Select External PHY
		NCRFullDX	= 0x08,	// Full duplex
		NCRLoopback	= 0x06,	// Internal PHY analog loopback
 
	RegNSR	= 0x01,	// Network Status Register
		NSRSpeed10	= 0x80,	// 0 = 100MBps, 1 = 10MBps (internal PHY)
		NSRLinkUp	= 0x40,	// 1 = link up (internal PHY)
		NSRTXFull	= 0x10,	// TX FIFO full
		NSRRXOver	= 0x08,	// RX FIFO overflow
 
	RegRCR	= 0x05,	// RX Control Register
		RCRDiscardLong	= 0x20,	// Discard long packet (over 1522 bytes)
		RCRDiscardCRC	= 0x10,	// Discard CRC error packet
		RCRAllMulticast	= 0x08,	// Pass all multicast
		RCRPromiscuous	= 0x02,	// Promiscuous
		RCRRXEnable		= 0x01,	// RX enable
 
	RegEPCR	= 0x0b,	// EEPROM & PHY Control Register
		EPCROpSelect	= 0x08,	// EEPROM or PHY Operation Select
		EPCRRegRead		= 0x04,	// EEPROM or PHY Register Read Command
		EPCRRegWrite	= 0x02,	// EEPROM or PHY Register Write Command
 
	RegEPAR	= 0x0c,	// EEPROM & PHY Address Register
		EPARIntPHY		= 0x40,	// [7:6] force to 01 if Internal PHY is selected
		EPARMask		= 0x1f,	// mask [0:5]
 
	RegEPDRL = 0x0d, // EEPROM & PHY Low Byte Data Register
 
	RegEPDRH = 0x0e, // EEPROM & PHY Low Byte Data Register
 
	RegPAR	= 0x10,	// [0x10 - 0x15] Physical Address Register
	
	RegMAR	= 0x16,	// [0x16 - 0x1d] Multicast Address Register
 
	RegGPCR	= 0x1E,	// General Purpose Control Register
		GPCRPowerDown	= 0x01,	// [0:6] Define in/out direction of GPCR
								// GPIO0 - is output for Power Down function
 
	RegGPR	= 0x1F,	// General Purpose Register
		GPRPowerDownInPHY = 0x01,	// Power down Internal PHY
 
	RegUSBC	= 0xf4, // USB Control Register
		USBCIntAck		= 0x20,	// ACK with 8-bytes of data on interrupt EP
		USBCIntNAck		= 0x10,	// Supress ACK on interrupt EP
 
};
 
 
enum MIIRegisters {
	RegBMCR	= 0x00,
		BMCRIsolate	= 0x0400,
		BMCRReset	= 0x8000,
 
	RegBMSR	= 0x01,
	RegPHYID1	= 0x02,
	RegPHYID2	= 0x03,
};
 
#define MII_OUI(id1, id2)       (((id1) << 6) | ((id2) >> 10))
#define MII_MODEL(id2)          (((id2) & 0x03f0) >> 4)
#define MII_REV(id2)             ((id2) & 0x000f)
 
 
DavicomDevice::DavicomDevice(usb_device device, DeviceInfo& deviceInfo)
	:	fDevice(device),
		fStatus(B_ERROR),
		fOpen(false),
		fRemoved(false),
		fHasConnection(false),
		fTXBufferFull(false),
		fNonBlocking(false),
		fInsideNotify(0),
		fNotifyEndpoint(0),
		fReadEndpoint(0),
		fWriteEndpoint(0),
		fMaxTXPacketSize(0),
		fActualLengthRead(0),
		fActualLengthWrite(0),
		fStatusRead(0),
		fStatusWrite(0),
		fNotifyReadSem(-1),
		fNotifyWriteSem(-1),
		fLinkStateChangeSem(-1),
		fNotifyData(NULL)
{
	fDeviceInfo = deviceInfo;
 
	memset(&fMACAddress, 0, sizeof(fMACAddress));
 
	fNotifyReadSem = create_sem(0, DRIVER_NAME"_notify_read");
	if (fNotifyReadSem < B_OK) {
		TRACE_ALWAYS("Error of creating read notify semaphore:%#010x\n",
															fNotifyReadSem);
		return;
	}
 
	fNotifyWriteSem = create_sem(0, DRIVER_NAME"_notify_write");
	if (fNotifyWriteSem < B_OK) {
		TRACE_ALWAYS("Error of creating write notify semaphore:%#010x\n",
															fNotifyWriteSem);
		return;
	}
 
	fNotifyData = new DM9601NotifyData();
	if (fNotifyData == NULL) {
		TRACE_ALWAYS("Error allocating notify buffer\n");
		return;
	}
 
	if (_SetupEndpoints() != B_OK) {
		return;
	}
 
	_InitMII();
 
	fStatus = B_OK;
	TRACE("Created!\n");
}
 
 
DavicomDevice::~DavicomDevice()
{
	if (fNotifyReadSem >= B_OK)
		delete_sem(fNotifyReadSem);
	if (fNotifyWriteSem >= B_OK)
		delete_sem(fNotifyWriteSem);
 
	if (!fRemoved) // ???
		gUSBModule->cancel_queued_transfers(fNotifyEndpoint);
 
	delete fNotifyData;
	TRACE("Deleted!\n");
}
 
 
status_t
DavicomDevice::Open(uint32 flags)
{
	if (fOpen)
		return B_BUSY;
	if (fRemoved)
		return B_ERROR;
 
	status_t result = _StartDevice();
	if (result != B_OK) {
		return result;
	}
 
	// setup state notifications
	result = gUSBModule->queue_interrupt(fNotifyEndpoint, fNotifyData,
		sizeof(DM9601NotifyData), _NotifyCallback, this);
	if (result != B_OK) {
		TRACE_ALWAYS("Error of requesting notify interrupt:%#010x\n", result);
		return result;
	}
 
	result = _EnableInterrupts(true);
 
	fNonBlocking = (flags & O_NONBLOCK) == O_NONBLOCK;
	fOpen = true;
	TRACE("Opened: %#010x!\n", result);
	return result;
}
 
 
status_t
DavicomDevice::Close()
{
	if (fRemoved) {
		fOpen = false;
		return B_OK;
	}
 
	_EnableInterrupts(false);
 
	// wait until possible notification handling finished...
	while (atomic_add(&fInsideNotify, 0) != 0)
		snooze(100);
	gUSBModule->cancel_queued_transfers(fNotifyEndpoint);
	gUSBModule->cancel_queued_transfers(fReadEndpoint);
	gUSBModule->cancel_queued_transfers(fWriteEndpoint);
 
	fOpen = false;
 
	status_t result = _StopDevice();
	TRACE("Closed: %#010x!\n", result);
	return result;
}
 
 
status_t
DavicomDevice::Free()
{
	TRACE("Freed!\n");
	return B_OK;
}
 
 
status_t
DavicomDevice::Read(uint8 *buffer, size_t *numBytes)
{
	size_t numBytesToRead = *numBytes;
	*numBytes = 0;
 
	if (fRemoved) {
		TRACE_ALWAYS("Error of receiving %d bytes from removed device.\n",
			numBytesToRead);
		return B_DEVICE_NOT_FOUND;
	}
 
	TRACE_RX("Request %d bytes.\n", numBytesToRead);
 
	struct _RXHeader {
		uint	FOE	:1;
		uint	CE	:1;
		uint	LE	:1;
		uint	PLE	:1;
		uint	RWTO:1;
		uint	LCS	:1;
		uint	MF	:1;
		uint	RF	:1;
		uint	countLow	:8;
		uint	countHigh	:8;
 
		uint8	Errors() { return 0xbf & *(uint8*)this; }
	} __attribute__((__packed__));
 
	_RXHeader header = { 0 };
 
	iovec rxData[] = {
		{ &header, sizeof(header) },
		{ buffer,  numBytesToRead }
	};
 
	status_t result = gUSBModule->queue_bulk_v(fReadEndpoint,
		rxData, 2, _ReadCallback, this);
	if (result != B_OK) {
		TRACE_ALWAYS("Error of queue_bulk_v request:%#010x\n", result);
		return result;
	}
 
	uint32 flags = B_CAN_INTERRUPT | (fNonBlocking ? B_TIMEOUT : 0);
	result = acquire_sem_etc(fNotifyReadSem, 1, flags, 0);
	if (result < B_OK) {
		TRACE_ALWAYS("Error of acquiring notify semaphore:%#010x.\n", result);
		return result;
	}
 
	if (fStatusRead != B_OK && fStatusRead != B_CANCELED && !fRemoved) {
		TRACE_ALWAYS("Device status error:%#010x\n", fStatusRead);
		return fStatusRead;
	}
 
	if (fActualLengthRead < sizeof(_RXHeader)) {
		TRACE_ALWAYS("Error: no place for RXHeader: only %d of %d bytes.\n",
			fActualLengthRead, sizeof(_RXHeader));
		return B_ERROR;
	}
 
	if (header.Errors() != 0) {
		TRACE_ALWAYS("RX header errors %#04x detected!\n", header.Errors());
	}
 
	TRACE_STATS("FOE:%d CE:%d LE:%d PLE:%d rwTO:%d LCS:%d MF:%d RF:%d\n",
			header.FOE, header.CE, header.LE, header.PLE,
			header.RWTO, header.LCS, header.MF, header.RF);
 
	*numBytes = header.countLow | ( header.countHigh << 8 );
 
	if (fActualLengthRead - sizeof(_RXHeader) > *numBytes) {
		TRACE_ALWAYS("MISMATCH of the frame length: hdr %d; received:%d\n",
			*numBytes, fActualLengthRead - sizeof(_RXHeader));
	}
 
	TRACE_RX("Read %d bytes.\n", *numBytes);
	return B_OK;
}
 
 
status_t
DavicomDevice::Write(const uint8 *buffer, size_t *numBytes)
{
	size_t numBytesToWrite = *numBytes;
	*numBytes = 0;
 
	if (fRemoved) {
		TRACE_ALWAYS("Error of writing %d bytes to removed device.\n",
			numBytesToWrite);
		return B_DEVICE_NOT_FOUND;
	}
 
	if (!fHasConnection) {
		TRACE_ALWAYS("Error of writing %d bytes to device while down.\n",
			numBytesToWrite);
		return B_ERROR;
	}
 
	if (fTXBufferFull) {
		TRACE_ALWAYS("Error of writing %d bytes to device: TX buffer full.\n",
			numBytesToWrite);
		return B_ERROR;
	}
 
	TRACE_TX("Write %d bytes.\n", numBytesToWrite);
 
	// additional padding byte must be transmitted in case data size
	// to be send is multiple of pipe's max packet size
	uint16 length = numBytesToWrite;
	size_t count = 2;
	if (((numBytesToWrite + 2) % fMaxTXPacketSize) == 0) {
		length++;
		count++;
	}
 
	struct _TXHeader {
		uint8	countLow;
		uint8	countHigh;
	} __attribute__((__packed__));
 
	_TXHeader header = { (uint8)(length & 0xff),
		(uint8)((length >> 8) & 0xff) };
 
	uint8 padding = 0;
 
	iovec txData[] = {
		{ &header, sizeof(_TXHeader) },
		{ (uint8*)buffer, numBytesToWrite },
		{ &padding, 1 }
	};
 
	status_t result = gUSBModule->queue_bulk_v(fWriteEndpoint,
		txData, count, _WriteCallback, this);
	if (result != B_OK) {
		TRACE_ALWAYS("Error of queue_bulk_v request:%#010x\n", result);
		return result;
	}
 
	result = acquire_sem_etc(fNotifyWriteSem, 1, B_CAN_INTERRUPT, 0);
 
	if (result < B_OK) {
		TRACE_ALWAYS("Error of acquiring notify semaphore:%#010x.\n", result);
		return result;
	}
 
	if (fStatusWrite != B_OK && fStatusWrite != B_CANCELED && !fRemoved) {
		TRACE_ALWAYS("Device status error:%#010x\n", fStatusWrite);
		return fStatusWrite;
	}
 
	*numBytes = fActualLengthWrite - sizeof(_TXHeader);
 
	TRACE_TX("Written %d bytes.\n", *numBytes);
	return B_OK;
}
 
 
status_t
DavicomDevice::Control(uint32 op, void *buffer, size_t length)
{
	switch (op) {
		case ETHER_INIT:
			return B_OK;
 
		case ETHER_GETADDR:
			memcpy(buffer, &fMACAddress, sizeof(fMACAddress));
			return B_OK;
 
		case ETHER_GETFRAMESIZE:
			*(uint32 *)buffer = kFrameSize;
			return B_OK;
 
		case ETHER_NONBLOCK:
			TRACE("ETHER_NONBLOCK\n");
			fNonBlocking = *((uint8*)buffer);
			return B_OK;
 
		case ETHER_SETPROMISC:
			TRACE("ETHER_SETPROMISC\n");
			return _SetPromiscuousMode(*((uint8*)buffer));
 
		case ETHER_ADDMULTI:
			TRACE("ETHER_ADDMULTI\n");
			return _ModifyMulticastTable(true, (ether_address_t*)buffer);
 
		case ETHER_REMMULTI:
			TRACE("ETHER_REMMULTI\n");
			return _ModifyMulticastTable(false, (ether_address_t*)buffer);
 
		case ETHER_SET_LINK_STATE_SEM:
			fLinkStateChangeSem = *(sem_id *)buffer;
			return B_OK;
 
		case ETHER_GET_LINK_STATE:
			return _GetLinkState((ether_link_state *)buffer);
 
		default:
			TRACE_ALWAYS("Unhandled IOCTL catched: %#010x\n", op);
	}
 
	return B_DEV_INVALID_IOCTL;
}
 
 
void
DavicomDevice::Removed()
{
	fRemoved = true;
	fHasConnection = false;
 
	// the notify hook is different from the read and write hooks as it does
	// itself schedule traffic (while the other hooks only release a semaphore
	// to notify another thread which in turn safly checks for the removed
	// case) - so we must ensure that we are not inside the notify hook anymore
	// before returning, as we would otherwise violate the promise not to use
	// any of the pipes after returning from the removed hook
	while (atomic_add(&fInsideNotify, 0) != 0)
		snooze(100);
 
	gUSBModule->cancel_queued_transfers(fNotifyEndpoint);
	gUSBModule->cancel_queued_transfers(fReadEndpoint);
	gUSBModule->cancel_queued_transfers(fWriteEndpoint);
 
	if (fLinkStateChangeSem >= B_OK)
		release_sem_etc(fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE);
}
 
 
status_t
DavicomDevice::SetupDevice(bool deviceReplugged)
{
	ether_address address;
	status_t result = _ReadMACAddress(&address);
	if (result != B_OK) {
		TRACE_ALWAYS("Error reading MAC address:%#010x\n", result);
		return result;
	}
 
	TRACE("MAC address is:%02x:%02x:%02x:%02x:%02x:%02x\n",
				address.ebyte[0], address.ebyte[1], address.ebyte[2],
				address.ebyte[3], address.ebyte[4], address.ebyte[5]);
 
	if (deviceReplugged) {
		// this might be the same device that was replugged - read the MAC
		// address (which should be at the same index) to make sure
		if (memcmp(&address, &fMACAddress, sizeof(address)) != 0) {
			TRACE_ALWAYS("Cannot replace device with MAC address:"
				"%02x:%02x:%02x:%02x:%02x:%02x\n",
				fMACAddress.ebyte[0], fMACAddress.ebyte[1],
				fMACAddress.ebyte[2], fMACAddress.ebyte[3],
				fMACAddress.ebyte[4], fMACAddress.ebyte[5]);
			return B_BAD_VALUE; // is not the same
		}
	} else
		fMACAddress = address;
 
	return B_OK;
}
 
 
status_t
DavicomDevice::CompareAndReattach(usb_device device)
{
	const usb_device_descriptor *deviceDescriptor
		= gUSBModule->get_device_descriptor(device);
 
	if (deviceDescriptor == NULL) {
		TRACE_ALWAYS("Error getting USB device descriptor.\n");
		return B_ERROR;
	}
 
	if (deviceDescriptor->vendor_id != fDeviceInfo.VendorId()
		&& deviceDescriptor->product_id != fDeviceInfo.ProductId()) {
		// this certainly isn't the same device
		return B_BAD_VALUE;
	}
 
	// this is the same device that was replugged - clear the removed state,
	// re-setup the endpoints and transfers and open the device if it was
	// previously opened
	fDevice = device;
	fRemoved = false;
	status_t result = _SetupEndpoints();
	if (result != B_OK) {
		fRemoved = true;
		return result;
	}
 
	// we need to setup hardware on device replug
	result = SetupDevice(true);
	if (result != B_OK) {
		return result;
	}
 
	if (fOpen) {
		fOpen = false;
		result = Open(fNonBlocking ? O_NONBLOCK : 0);
	}
 
	return result;
}
 
 
status_t
DavicomDevice::_SetupEndpoints()
{
	const usb_configuration_info *config
		= gUSBModule->get_nth_configuration(fDevice, 0);
 
	if (config == NULL) {
		TRACE_ALWAYS("Error of getting USB device configuration.\n");
		return B_ERROR;
	}
 
	if (config->interface_count <= 0) {
		TRACE_ALWAYS("Error:no interfaces found in USB device configuration\n");
		return B_ERROR;
	}
 
	usb_interface_info *interface = config->interface[0].active;
	if (interface == 0) {
		TRACE_ALWAYS("Error:invalid active interface in "
												"USB device configuration\n");
		return B_ERROR;
	}
 
	int notifyEndpoint = -1;
	int readEndpoint   = -1;
	int writeEndpoint  = -1;
 
	for (size_t ep = 0; ep < interface->endpoint_count; ep++) {
		usb_endpoint_descriptor *epd = interface->endpoint[ep].descr;
		if ((epd->attributes & USB_ENDPOINT_ATTR_MASK)
				== USB_ENDPOINT_ATTR_INTERRUPT)
		{
			notifyEndpoint = ep;
			continue;
		}
 
		if ((epd->attributes & USB_ENDPOINT_ATTR_MASK)
				!= USB_ENDPOINT_ATTR_BULK)
		{
			TRACE_ALWAYS("Error: USB endpoint type %#04x is unknown.\n",
					epd->attributes);
			continue;
		}
 
		if ((epd->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN)
				== USB_ENDPOINT_ADDR_DIR_IN)
		{
			readEndpoint = ep;
			continue;
		}
 
		if ((epd->endpoint_address & USB_ENDPOINT_ADDR_DIR_OUT)
				== USB_ENDPOINT_ADDR_DIR_OUT)
		{
			writeEndpoint = ep;
			continue;
		}
	}
 
	if (notifyEndpoint == -1 || readEndpoint == -1 || writeEndpoint == -1) {
		TRACE_ALWAYS("Error: not all USB endpoints were found: notify:%d; "
			"read:%d; write:%d\n", notifyEndpoint, readEndpoint, writeEndpoint);
		return B_ERROR;
	}
 
	gUSBModule->set_configuration(fDevice, config);
 
	fNotifyEndpoint = interface->endpoint[notifyEndpoint].handle;
	fReadEndpoint   = interface->endpoint[readEndpoint  ].handle;
	fWriteEndpoint  = interface->endpoint[writeEndpoint ].handle;
	fMaxTXPacketSize = interface->endpoint[writeEndpoint].descr->max_packet_size;
 
	return B_OK;
}
 
 
status_t
DavicomDevice::_ReadMACAddress(ether_address_t *address)
{
	status_t result = _ReadRegister(RegPAR,
							sizeof(ether_address), (uint8*)address);
	if (result != B_OK) {
		TRACE_ALWAYS("Error of reading MAC address:%#010x\n", result);
		return result;
	}
 
	return B_OK;
}
 
 
status_t
DavicomDevice::_StartDevice()
{
	uint8 control = 0;
 
	// disable loopback
	status_t result = _ReadRegister(RegNCR, 1, &control);
	if (result != B_OK) {
		TRACE_ALWAYS("Error reading NCR: %#010x.\n", result);
		return result;
	}
 
	if (control & NCRExtPHY)
		TRACE_ALWAYS("Device uses external PHY\n");
 
	control &= ~NCRLoopback;
	result = _Write1Register(RegNCR, control);
	if (result != B_OK) {
		TRACE_ALWAYS("Error writing %#02X to NCR: %#010x.\n", control, result);
		return result;
	}
 
	// Initialize RX control register, enable RX and activate multicast
	result = _ReadRegister(RegRCR, 1, &control);
	if (result != B_OK) {
		TRACE_ALWAYS("Error reading RCR: %#010x.\n", result);
		return result;
	}
 
	control &= ~RCRPromiscuous;
	control |= RCRDiscardLong | RCRDiscardCRC | RCRRXEnable | RCRAllMulticast;
	result = _Write1Register(RegRCR, control);
	if (result != B_OK) {
		TRACE_ALWAYS("Error writing %#02X to RCR: %#010x.\n", control, result);
		return result;
	}
 
	// clear POWER_DOWN state of internal PHY
	result = _ReadRegister(RegGPCR, 1, &control);
	if (result != B_OK) {
		TRACE_ALWAYS("Error reading GPCR: %#010x.\n", result);
		return result;
	}
 
	control |= GPCRPowerDown;
	result = _Write1Register(RegGPCR, control);
	if (result != B_OK) {
		TRACE_ALWAYS("Error writing %#02X to GPCR: %#010x.\n", control, result);
		return result;
	}
 
	result = _ReadRegister(RegGPR, 1, &control);
	if (result != B_OK) {
		TRACE_ALWAYS("Error reading GPR: %#010x.\n", result);
		return result;
	}
 
	control &= ~GPRPowerDownInPHY;
	result = _Write1Register(RegGPR, control);
	if (result != B_OK) {
		TRACE_ALWAYS("Error writing %#02X to GPR: %#010x.\n", control, result);
		return result;
	}
 
	return B_OK;
}
 
 
status_t
DavicomDevice::_StopDevice()
{
	uint8 control = 0;
 
	// disable RX
	status_t result = _ReadRegister(RegRCR, 1, &control);
	if (result != B_OK) {
		TRACE_ALWAYS("Error reading RCR: %#010x.\n", result);
		return result;
	}
 
	control &= ~RCRRXEnable;
	result = _Write1Register(RegRCR, control);
	if (result != B_OK)
		TRACE_ALWAYS("Error writing %#02X to RCR: %#010x.\n", control, result);
 
	return result;
}
 
 
status_t
DavicomDevice::_SetPromiscuousMode(bool on)
{
	uint8 control = 0;
 
	status_t result = _ReadRegister(RegRCR, 1, &control);
	if (result != B_OK) {
		TRACE_ALWAYS("Error reading RCR: %#010x.\n", result);
		return result;
	}
 
	if (on)
		control |= RCRPromiscuous;
	else
		control &= ~RCRPromiscuous;
 
	result = _Write1Register(RegRCR, control);
	if (result != B_OK)
		TRACE_ALWAYS("Error writing %#02X to RCR: %#010x.\n", control, result);
 
	return result;
}
 
 
uint32
DavicomDevice::_EthernetCRC32(const uint8* buffer, size_t length)
{
	uint32 result = 0xffffffff;
	for (size_t i = 0; i < length; i++) {
		uint8 data = *buffer++;
		for (int bit = 0; bit < 8; bit++, data >>= 1) {
			uint32 carry = ((result & 0x80000000) ? 1 : 0) ^ (data & 0x01);
			result <<= 1;
			if (carry != 0)
				result = (result ^ 0x04c11db6) | carry;
		}
	}
	return result;
}
 
 
status_t
DavicomDevice::_ModifyMulticastTable(bool join, ether_address_t *group)
{
	char groupName[6 * 3 + 1] = { 0 };
	sprintf(groupName, "%02x:%02x:%02x:%02x:%02x:%02x",
		group->ebyte[0], group->ebyte[1], group->ebyte[2],
		group->ebyte[3], group->ebyte[4], group->ebyte[5]);
	TRACE("%s multicast group %s\n", join ? "Joining" : "Leaving", groupName);
 
	uint32 hash = _EthernetCRC32(group->ebyte, 6);
	bool isInTable = fMulticastHashes.Find(hash) != fMulticastHashes.End();
	
	if (isInTable && join)
		return B_OK; // already listed - nothing to do
 
	if (!isInTable && !join) {
		TRACE_ALWAYS("Cannot leave unlisted multicast group %s!\n", groupName);
		return B_ERROR;
	}
 
	const size_t hashLength = 8;
	uint8 hashTable[hashLength] = { 0 };
	hashTable[hashLength - 1] |= 0x80; // broadcast address
 
	status_t result = _WriteRegister(RegMAR, hashLength, hashTable);
	if (result != B_OK) {
		TRACE_ALWAYS("Error initializing MAR: %#010x.\n", result);
		return result;
	}
	
	if (join)
		fMulticastHashes.PushBack(hash);
	else
		fMulticastHashes.Remove(hash);
 
	for (int32 i = 0; i < fMulticastHashes.Count(); i++) {
		uint32 hash = fMulticastHashes[i] >> 26;
		hashTable[hash / 8] |= 1 << (hash % 8);
	}
 
	// clear/set pass all multicast bit as required
	uint8 control = 0;
	result = _ReadRegister(RegRCR, 1, &control);
	if (result != B_OK) {
		TRACE_ALWAYS("Error reading RCR: %#010x.\n", result);
		return result;
	}
 
	if (fMulticastHashes.Count() > 0)
		control &= ~RCRAllMulticast;
	else
		control |= RCRAllMulticast;
 
	result = _Write1Register(RegRCR, control);
	if (result != B_OK) {
		TRACE_ALWAYS("Error writing %#02X to RCR: %#010x.\n", control, result);
		return result;
	}
 
	result = _WriteRegister(RegMAR, hashLength, hashTable);
	if (result != B_OK)
		TRACE_ALWAYS("Error writing hash table in MAR: %#010x.\n", result);
 
	return result;
}
 
 
void
DavicomDevice::_ReadCallback(void *cookie, int32 status, void *data,
	size_t actualLength)
{
	TRACE_RX("ReadCB: %d bytes; status:%#010x\n", actualLength, status);
	DavicomDevice *device = (DavicomDevice *)cookie;
	device->fActualLengthRead = actualLength;
	device->fStatusRead = status;
	device->fStats.readCount++;
	release_sem_etc(device->fNotifyReadSem, 1, B_DO_NOT_RESCHEDULE);
}
 
 
void
DavicomDevice::_WriteCallback(void *cookie, int32 status, void *data,
	size_t actualLength)
{
	TRACE_TX("WriteCB: %d bytes; status:%#010x\n", actualLength, status);
	DavicomDevice *device = (DavicomDevice *)cookie;
	device->fActualLengthWrite = actualLength;
	device->fStatusWrite = status;
	device->fStats.writeCount++;
	release_sem_etc(device->fNotifyWriteSem, 1, B_DO_NOT_RESCHEDULE);
}
 
 
void
DavicomDevice::_NotifyCallback(void *cookie, int32 status, void *data,
	size_t actualLength)
{
	DavicomDevice *device = (DavicomDevice *)cookie;
	atomic_add(&device->fInsideNotify, 1);
	if (status == B_CANCELED || device->fRemoved) {
		atomic_add(&device->fInsideNotify, -1);
		return;
	}
 
	if (status == B_OK)
		device->_OnNotify(actualLength);
	else
		TRACE_ALWAYS("Status error:%#010x; length:%d\n", status, actualLength);
 
	// schedule next notification buffer
	gUSBModule->queue_interrupt(device->fNotifyEndpoint, device->fNotifyData,
		sizeof(DM9601NotifyData), _NotifyCallback, device);
	atomic_add(&device->fInsideNotify, -1);
}
 
 
status_t
DavicomDevice::_OnNotify(uint32 actualLength)
{
	if (actualLength != sizeof(DM9601NotifyData)) {
		TRACE_ALWAYS("Data underrun error. %d of %d bytes received\n",
			actualLength, sizeof(DM9601NotifyData));
		return B_BAD_DATA;
	}
 
	bool linkIsUp = fNotifyData->LINKST != 0;
	fTXBufferFull = fNotifyData->TXFULL != 0;
	bool rxOverflow = fNotifyData->RXOV != 0;
 
	bool linkStateChange = (linkIsUp != fHasConnection);
	fHasConnection = linkIsUp;
 
	if (linkStateChange) {
		if (fHasConnection) {
			TRACE("Link is now up at %s Mb/s\n",
				fNotifyData->SPEED ? "10" : "100");
		} else
			TRACE("Link is now down");
	}
 
#ifdef UDAV_TRACE
	if (gTraceStats) {
		if (fNotifyData->TXFULL)
			fStats.txFull++;
		if (fNotifyData->RXOV)
			fStats.rxOverflow++;
 
		if (fNotifyData->ROC)
			fStats.rxOvCount += fNotifyData->ROC;
 
		if (fNotifyData->RT)
			fStats.runtFrames++;
		if (fNotifyData->LCS)
			fStats.lateRXCollisions++;
		if (fNotifyData->RWTO)
			fStats.rwTOs++;
		if (fNotifyData->PLE)
			fStats.physLayerErros++;
		if (fNotifyData->AE)
			fStats.alignmentErros++;
		if (fNotifyData->CE)
			fStats.crcErrors++;
		if (fNotifyData->FOE)
			fStats.overErrors++;
 
		if (fNotifyData->TSR1.LC)
			fStats.lateTXCollisions++;
		if (fNotifyData->TSR1.LCR)
			fStats.lostOfCarrier++;
		if (fNotifyData->TSR1.NC)
			fStats.noCarrier++;
		if (fNotifyData->TSR1.COL)
			fStats.txCollisions++;
		if (fNotifyData->TSR1.EC)
			fStats.excCollisions++;
 
		if (fNotifyData->TSR2.LC)
			fStats.lateTXCollisions++;
		if (fNotifyData->TSR2.LCR)
			fStats.lostOfCarrier++;
		if (fNotifyData->TSR2.NC)
			fStats.noCarrier++;
		if (fNotifyData->TSR2.COL)
			fStats.txCollisions++;
		if (fNotifyData->TSR2.EC)
			fStats.excCollisions++;
 
		fStats.notifyCount++;
	}
#endif
 
	if (rxOverflow)
		TRACE("RX buffer overflow. %d packets dropped\n", fNotifyData->ROC);
 
	uint8 tsr = 0xfc & *(uint8*)&fNotifyData->TSR1;
	if (tsr != 0)
		TRACE("TX packet 1: Status %#04x is not OK.\n", tsr);
 
	tsr = 0xfc & *(uint8*)&fNotifyData->TSR2;
	if (tsr != 0)
		TRACE("TX packet 2: Status %#04x is not OK.\n", tsr);
 
	if (linkStateChange && fLinkStateChangeSem >= B_OK)
		release_sem_etc(fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE);
	return B_OK;
}
 
 
status_t
DavicomDevice::_GetLinkState(ether_link_state *linkState)
{
	uint8 registerValue = 0;
	status_t result = _ReadRegister(RegNSR, 1, &registerValue);
	if (result != B_OK) {
		TRACE_ALWAYS("Error reading NSR register! %x\n", result);
		return result;
	}
 
	if (registerValue & NSRSpeed10)
		linkState->speed = 10000000;
	else
		linkState->speed = 100000000;
 
	linkState->quality = 1000;
 
	linkState->media = IFM_ETHER | IFM_100_TX;
	if (fHasConnection) {
		linkState->media |= IFM_ACTIVE;
		result = _ReadRegister(RegNCR, 1, &registerValue);
		if (result != B_OK) {
			TRACE_ALWAYS("Error reading NCR register! %x\n", result);
			return result;
		}
 
		if (registerValue & NCRFullDX)
			linkState->media |= IFM_FULL_DUPLEX;
		else
			linkState->media |= IFM_HALF_DUPLEX;
 
		if (registerValue & NCRLoopback)
			linkState->media |= IFM_LOOP;
	}
 
	TRACE_STATE("Medium state: %s, %lld MBit/s, %s duplex.\n",
						(linkState->media & IFM_ACTIVE) ? "active" : "inactive",
						linkState->speed / 1000000,
						(linkState->media & IFM_FULL_DUPLEX) ? "full" : "half");
 
	TRACE_STATS("tx:%d rx:%d rxCn:%d rtF:%d lRxC:%d rwTO:%d PLE:%d AE:%d CE:%d "
				"oE:%d ltxC:%d lCR:%d nC:%d txC:%d exC:%d r:%d w:%d n:%d\n",
					fStats.txFull, fStats.rxOverflow, fStats.rxOvCount,
					fStats.runtFrames, fStats.lateRXCollisions, fStats.rwTOs,
					fStats.physLayerErros, fStats.alignmentErros,
					fStats.crcErrors, fStats.overErrors,
					fStats.lateTXCollisions, fStats.lostOfCarrier,
					fStats.noCarrier, fStats.txCollisions, fStats.excCollisions,
					fStats.readCount, fStats.writeCount, fStats.notifyCount);
	return B_OK;
}
 
 
status_t
DavicomDevice::_ReadRegister(uint8 reg, size_t size, uint8* buffer)
{
	if (size > 255)
		return B_BAD_VALUE;
 
	size_t actualLength = 0;
	status_t result = gUSBModule->send_request(fDevice,
		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_IN,
		ReqReadRegister, 0, reg, size, buffer, &actualLength);
 
	if (size != actualLength) {
		TRACE_ALWAYS("Size mismatch reading register ! asked %d got %d",
			size, actualLength);
	}
 
	return result;
}
 
 
status_t
DavicomDevice::_WriteRegister(uint8 reg, size_t size, uint8* buffer)
{
	if (size > 255)
		return B_BAD_VALUE;
 
	size_t actualLength = 0;
 
	status_t result = gUSBModule->send_request(fDevice,
		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT,
		ReqWriteRegister, 0, reg, size, buffer, &actualLength);
 
	return result;
}
 
 
status_t
DavicomDevice::_Write1Register(uint8 reg, uint8 value)
{
	size_t actualLength = 0;
 
	status_t result = gUSBModule->send_request(fDevice,
		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT,
		ReqWriteRegisterByte, value, reg, 0, NULL, &actualLength);
 
	return result;
}
 
 
status_t
DavicomDevice::_ReadMII(uint8 reg, uint16* data)
{
	// select PHY and set PHY register address
	status_t result = _Write1Register(RegEPAR, EPARIntPHY | (reg & EPARMask));
	if (result != B_OK) {
		TRACE_ALWAYS("Failed to set MII address %#x. Error:%#x\n", reg, result);
		return result;
	}
 
	// select PHY operation and initiate reading
	result = _Write1Register(RegEPCR, EPCROpSelect | EPCRRegRead);
	if (result != B_OK) {
		TRACE_ALWAYS("Failed to starting MII reading. Error:%#x\n", result);
		return result;
	}
 
	// finalize writing
	uint8 control = 0;
	result = _ReadRegister(RegEPCR, 1, &control);
	if (result != B_OK) {
		TRACE_ALWAYS("Failed to read EPCR register. Error:%#x\n", result);
		return result;
	}
 
	result = _Write1Register(RegEPCR, control & ~EPCRRegRead);
	if (result != B_OK) {
		TRACE_ALWAYS("Failed to write EPCR register. Error:%#x\n", result);
		return result;
	}
 
	// retrieve the result from data registers
	uint8 values[2] = { 0 };
	result = _ReadRegister(RegEPDRL, 2, values);
	if (result != B_OK) {
		TRACE_ALWAYS("Failed to retrieve data %#x. Error:%#x\n", data, result);
		return result;
	}
 
	*data = values[0] | values[1] << 8;
	return result;
}
 
 
status_t
DavicomDevice::_WriteMII(uint8 reg, uint16 data)
{
	// select PHY and set PHY register address
	status_t result = _Write1Register(RegEPAR, EPARIntPHY | (reg & EPARMask));
	if (result != B_OK) {
		TRACE_ALWAYS("Failed to set MII address %#x. Error:%#x\n", reg, result);
		return result;
	}
 
	// put the value to data register
	uint8 values[] = { (uint8)(data & 0xff), (uint8)((data >> 8) & 0xff) };
	result = _WriteRegister(RegEPDRL, sizeof(uint16), values);
	if (result != B_OK) {
		TRACE_ALWAYS("Failed to put data %#x. Error:%#x\n", data, result);
		return result;
	}
 
	// select PHY operation and initiate writing
	result = _Write1Register(RegEPCR, EPCROpSelect | EPCRRegWrite);
	if (result != B_OK) {
		TRACE_ALWAYS("Failed to starting MII wrintig. Error:%#x\n", result);
		return result;
	}
 
	// finalize writing
	uint8 control = 0;
	result = _ReadRegister(RegEPCR, 1, &control);
	if (result != B_OK) {
		TRACE_ALWAYS("Failed to read EPCR register. Error:%#x\n", result);
		return result;
	}
 
	result = _Write1Register(RegEPCR, control & ~EPCRRegWrite);
	if (result != B_OK)
		TRACE_ALWAYS("Failed to write EPCR register. Error:%#x\n", result);
 
	return result;
}
 
 
status_t
DavicomDevice::_InitMII()
{
	uint16 control = 0;
	status_t result = _ReadMII(RegBMCR, &control);
	if (result != B_OK) {
		TRACE_ALWAYS("Failed to read MII BMCR register. Error:%#x\n", result);
		return result;
	}
 
	result = _WriteMII(RegBMCR, control & ~BMCRIsolate);
	if (result != B_OK) {
		TRACE_ALWAYS("Failed to clear isolate PHY. Error:%#x\n", result);
		return result;
	}
 
	result = _WriteMII(0, BMCRReset);
	if (result != B_OK) {
		TRACE_ALWAYS("Failed to reset BMCR register. Error:%#x\n", result);
		return result;
	}
 
	uint16 id01 = 0, id02 = 0;
	result = _ReadMII(RegPHYID1, &id01);
	if (result != B_OK) {
		TRACE_ALWAYS("Failed to read PHY ID 0. Error:%#x\n", result);
		return result;
	}
 
	result = _ReadMII(RegPHYID2, &id02);
	if (result != B_OK) {
		TRACE_ALWAYS("Failed to read PHY ID 1. Error:%#x\n", result);
		return result;
	}
 
	TRACE_ALWAYS("MII Info: OUI:%04x; Model:%04x; rev:%02x.\n",
			MII_OUI(id01, id02), MII_MODEL(id02), MII_REV(id02));
 
	return result;
}
 
 
status_t
DavicomDevice::_EnableInterrupts(bool enable)
{
	uint8 control = 0;
	status_t result = _ReadRegister(RegUSBC, 1, &control);
	if (result != B_OK) {
		TRACE_ALWAYS("Error of reading USB control register:%#010x\n", result);
		return result;
	}
 
	if (enable) {
		control |= USBCIntAck;
		control &= ~USBCIntNAck;
	} else {
		control &= ~USBCIntAck;
	}
 
	result = _Write1Register(RegUSBC, control);
	if (result != B_OK)
		TRACE_ALWAYS("Error of setting USB control register:%#010x\n", result);
 
	return result;
}
 

V547 Expression '(epd->endpoint_address & 0x00) == 0x00' is always true.