/*
 * Copyright 2009-2010, François Revol, <revol@free.fr>.
 * Sponsored by TuneTracker Systems.
 * Based on the Haiku usb_serial driver which is:
 *
 * Copyright (c) 2007-2008 by Michael Lotz
 * Heavily based on the original usb_serial driver which is:
 *
 * Copyright (c) 2003 by Siarzhuk Zharski <imker@gmx.li>
 * Distributed under the terms of the MIT License.
 */
#include "SerialDevice.h"
#include "UART.h"
 
#include <sys/ioctl.h>
 
SerialDevice::SerialDevice(const struct serial_support_descriptor *device,
	uint32 ioBase, uint32 irq, const SerialDevice *master)
	:	/*fSupportDescriptor(device->descriptor),
		fDevice(device),
		fDescription(device->descriptor->name),*/
		fSupportDescriptor(device),
		fDevice(NULL),
		fDescription(device->name),
		//
		fDeviceOpen(false),
		fDeviceRemoved(false),
		fBus(device->bus),
		fIOBase(ioBase),
		fIRQ(irq),
		fMaster(master),
		fCachedIER(0x0),
		fCachedIIR(0x1),
		fPendingDPC(0),
		fReadBufferAvail(0),
		fReadBufferIn(0),
		fReadBufferOut(0),
		fReadBufferSem(-1),
		fWriteBufferAvail(0),
		fWriteBufferIn(0),
		fWriteBufferOut(0),
		fWriteBufferSem(-1),
		fDoneRead(-1),
		fDoneWrite(-1),
		fControlOut(0),
		fInputStopped(false),
		fMasterTTY(NULL),
		fSlaveTTY(NULL),
		fSystemTTYCookie(NULL),
		fDeviceTTYCookie(NULL),
		fDeviceThread(-1),
		fStopDeviceThread(false)
{
	memset(&fTTYConfig, 0, sizeof(termios));
	fTTYConfig.c_cflag = B9600 | CS8 | CREAD;
	memset(fReadBuffer, 'z', DEF_BUFFER_SIZE);
	memset(fWriteBuffer, 'z', DEF_BUFFER_SIZE);
}
 
 
SerialDevice::~SerialDevice()
{
	Removed();
 
	if (fDoneWrite >= B_OK)
		delete_sem(fDoneWrite);
	if (fReadBufferSem >= B_OK)
		delete_sem(fReadBufferSem);
	if (fWriteBufferSem >= B_OK)
		delete_sem(fWriteBufferSem);
}
 
 
bool
SerialDevice::Probe()
{
	uint8 msr;
	msr = ReadReg8(MSR);
	// just in case read twice to make sure the "delta" bits are 0
	msr = ReadReg8(MSR);
	// this should be enough to probe for the device for now
	// we might want to check the scratch reg, and try identifying
	// the model as in:
	// http://en.wikibooks.org/wiki/Serial_Programming/8250_UART_Programming#Software_Identification_of_the_UART
	return (msr != 0xff);
}
 
 
status_t
SerialDevice::Init()
{
	fDoneWrite = create_sem(0, "pc_serial:done_write");
	fReadBufferSem = create_sem(0, "pc_serial:done_read");
	fWriteBufferSem = create_sem(0, "pc_serial:done_write");
 
	// disable IRQ
	fCachedIER = 0;
	WriteReg8(IER, fCachedIER);
 
	// disable DLAB
	WriteReg8(LCR, 0);
 
	return B_OK;
}
 
 
void
SerialDevice::SetModes(struct termios *tios)
{
	//TRACE_FUNCRES(trace_termios, tios);
	spin(10000);
	uint32 baudIndex = tios->c_cflag & CBAUD;
	if (baudIndex > BLAST)
		baudIndex = BLAST;
 
	// update our master config in full
	memcpy(&fTTYConfig, tios, sizeof(termios));
	fTTYConfig.c_cflag &= ~CBAUD;
	fTTYConfig.c_cflag |= baudIndex;
 
	// only apply the relevant parts to the device side
	termios config;
	memset(&config, 0, sizeof(termios));
	config.c_cflag = tios->c_cflag;
	config.c_cflag &= ~CBAUD;
	config.c_cflag |= baudIndex;
 
	// update the termios of the device side
	gTTYModule->tty_control(fDeviceTTYCookie, TCSETA, &config, sizeof(termios));
 
	uint8 lcr = 0;
	uint16 divisor = SupportDescriptor()->bauds[baudIndex];
 
	switch (tios->c_cflag & CSIZE) {
#if	CS5 != CS7
	// in case someday...
	case CS5:
		lcr |= LCR_5BIT;
		break;
	case CS6:
		lcr |= LCR_6BIT;
		break;
#endif
	case CS7:
		lcr |= LCR_7BIT;
		break;
	case CS8:
	default:
		lcr |= LCR_8BIT;
		break;
	}
 
	if (tios->c_cflag & CSTOPB)
		lcr |= LCR_2STOP;
	if (tios->c_cflag & PARENB)
		lcr |= LCR_P_EN;
	if ((tios->c_cflag & PARODD) == 0)
		lcr |= LCR_P_EVEN;
 
	if (baudIndex == B0) {
		// disable
		MaskReg8(MCR, MCR_DTR);
	} else {
		// set FCR now, 
		// 16650 and later chips have another reg at 2 when DLAB=1
		uint8 fcr = FCR_ENABLE | FCR_RX_RST | FCR_TX_RST | FCR_F_8 | FCR_F64EN;
		// enable fifo
		//fcr = 0;
		WriteReg8(FCR, fcr);
 
		// unmask the divisor latch regs
		WriteReg8(LCR, lcr | LCR_DLAB);
		// set divisor
		WriteReg8(DLLB, divisor & 0x00ff);
		WriteReg8(DLHB, divisor >> 8);
		//WriteReg8(2,0);
 
	}
	// set control lines, and disable divisor latch reg
	WriteReg8(LCR, lcr);
 
 
#if 0
	uint16 newControl = fControlOut;
 
	static uint32 baudRates[] = {
		0x00000000, //B0
		0x00000032, //B50
		0x0000004B, //B75
		0x0000006E, //B110
		0x00000086, //B134
		0x00000096, //B150
		0x000000C8, //B200
		0x0000012C, //B300
		0x00000258, //B600
		0x000004B0, //B1200
		0x00000708, //B1800
		0x00000960, //B2400
		0x000012C0, //B4800
		0x00002580, //B9600
		0x00004B00, //B19200
		0x00009600, //B38400
		0x0000E100, //B57600
		0x0001C200, //B115200
		0x00038400, //B230400
		0x00070800, //460800
		0x000E1000, //921600
	};
 
	usb_serial_line_coding lineCoding;
	lineCoding.speed = baudRates[baudIndex];
	lineCoding.stopbits = (tios->c_cflag & CSTOPB) ? LC_STOP_BIT_2 : LC_STOP_BIT_1;
 
	if (tios->c_cflag & PARENB) {
		lineCoding.parity = LC_PARITY_EVEN;
		if (tios->c_cflag & PARODD)
			lineCoding.parity = LC_PARITY_ODD;
	} else
		lineCoding.parity = LC_PARITY_NONE;
 
	lineCoding.databits = (tios->c_cflag & CS8) ? 8 : 7;
 
	if (lineCoding.speed == 0) {
		newControl &= 0xfffffffe;
		lineCoding.speed = fLineCoding.speed;
	} else
		newControl = CLS_LINE_DTR;
 
	if (fControlOut != newControl) {
		fControlOut = newControl;
		TRACE("newctrl send to modem: 0x%08x\n", newControl);
		SetControlLineState(newControl);
	}
 
	if (memcmp(&lineCoding, &fLineCoding, sizeof(usb_serial_line_coding)) != 0) {
		fLineCoding.speed = lineCoding.speed;
		fLineCoding.stopbits = lineCoding.stopbits;
		fLineCoding.databits = lineCoding.databits;
		fLineCoding.parity = lineCoding.parity;
		TRACE("send to modem: speed %d sb: 0x%08x db: 0x%08x parity: 0x%08x\n",
			fLineCoding.speed, fLineCoding.stopbits, fLineCoding.databits,
			fLineCoding.parity);
		SetLineCoding(&fLineCoding);
	}
#endif
}
 
 
bool
SerialDevice::Service(struct tty *tty, uint32 op, void *buffer, size_t length)
{
	uint8 msr;
	status_t err;
 
	TRACE("%s(,0x%08lx,,%d)\n", __FUNCTION__, op, length);
	if (tty != fMasterTTY)
		return false;
 
	TRACE("%s(,0x%08lx,,%d)\n", __FUNCTION__, op, length);
 
	switch (op) {
		case TTYENABLE:
		{
			bool enable = *(bool *)buffer;
			TRACE("TTYENABLE: %sable\n", enable ? "en" : "dis");
 
			if (enable) {
				//XXX:must call SetModes();
				err = install_io_interrupt_handler(IRQ(), pc_serial_interrupt, this, 0);
				TRACE("installing irq handler for %d: %s\n", IRQ(), strerror(err));
			} else {
				// remove the handler
				remove_io_interrupt_handler(IRQ(), pc_serial_interrupt, this);
				// disable IRQ
				fCachedIER = 0;
				WriteReg8(IER, fCachedIER);
				WriteReg8(MCR, 0);
			}
 
			msr = ReadReg8(MSR);
 
			SignalControlLineState(TTYHWDCD, msr & MSR_DCD);
			SignalControlLineState(TTYHWCTS, msr & MSR_CTS);
 
			if (enable) {
				// 
				WriteReg8(MCR, MCR_DTR | MCR_RTS | MCR_IRQ_EN /*| MCR_LOOP*//*XXXXXXX*/);
				// enable irqs
				fCachedIER = IER_RLS | IER_MS | IER_RDA;
				WriteReg8(IER, fCachedIER);
				//WriteReg8(IER, IER_RDA);
			}
 
			return true;
		}
 
		case TTYISTOP:
			fInputStopped = *(bool *)buffer;
			TRACE("TTYISTOP: %sstopped\n", fInputStopped ? "" : "not ");
 
			if (fInputStopped)
				MaskReg8(MCR, MCR_RTS);
			else
				OrReg8(MCR, MCR_RTS);
 
			//gTTYModule->ttyhwsignal(ptty, ddr, TTYHWCTS, false);
			//SignalControlLineState(TTYHWCTS, !fInputStopped);
			//msr = ReadReg8(MSR);
			//SignalControlLineState(TTYHWCTS, msr & MSR_CTS);
			return true;
 
		case TTYGETSIGNALS:
			TRACE("TTYGETSIGNALS\n");
			msr = ReadReg8(MSR);
			SignalControlLineState(TTYHWDCD, msr & MSR_DCD);
			SignalControlLineState(TTYHWCTS, msr & MSR_CTS);
			SignalControlLineState(TTYHWDSR, msr & MSR_DSR);
			SignalControlLineState(TTYHWRI, msr & MSR_RI);
			return true;
 
		case TTYSETMODES:
			TRACE("TTYSETMODES\n");
			SetModes((struct termios *)buffer);
//WriteReg8(IER, IER_RLS | IER_MS | IER_RDA);
			return true;
 
		case TTYSETDTR:
		case TTYSETRTS:
		{
			bool set = *(bool *)buffer;
			uint8 bit = op == TTYSETDTR ? MCR_DTR : MCR_RTS;
			if (set)
				OrReg8(MCR, bit);
			else
				MaskReg8(MCR, bit);
 
			return true;
		}
 
		case TTYOSTART:
			TRACE("TTYOSTART\n");
			// enable irqs
			fCachedIER |= IER_THRE;
			// XXX: toggle the bit to make VirtualBox happy !?
			WriteReg8(IER, fCachedIER & ~IER_THRE);
			WriteReg8(IER, fCachedIER);
			return true;
		case TTYOSYNC:
			TRACE("TTYOSYNC\n");
			return (ReadReg8(LSR) & (LSR_THRE | LSR_TSRE)) == (LSR_THRE | LSR_TSRE);
			return true;
		case TTYSETBREAK:
		{
			bool set = *(bool *)buffer;
			if (set)
				OrReg8(MCR, LCR_BREAK);
			else
				MaskReg8(MCR, LCR_BREAK);
 
			return true;
		}
		default:
			return false;
	}
 
	return false;
}
 
 
bool
SerialDevice::IsInterruptPending()
{
	TRACE(("IsInterruptPending()\n"));
 
	// because reading the IIR acknowledges some IRQ conditions,
	// the next time we'll read we'll miss the IRQ condition
	// so we just cache the value for the real handler
	fCachedIIR = ReadReg8(IIR);
 
	bool pending = (fCachedIIR & IIR_PENDING) == 0;
 
	if (pending) {
		// temporarily mask the IRQ
		// else VirtualBox triggers one per every written byte it seems
		// not sure it's required on real hardware
		WriteReg8(IER, fCachedIER & ~(IER_RLS | IER_MS | IER_RDA | IER_THRE));
 
		atomic_add(&fPendingDPC, 1);
	}
 
	return pending; // 0 means yes
}
 
 
int32
SerialDevice::InterruptHandler()
{
	int32 ret = B_UNHANDLED_INTERRUPT;
	//XXX: what should we do here ? (certainly not use a mutex !)
 
	uint8 iir, lsr, msr;
	uint8 buffer[64];
	int tries = 8; // avoid busy looping
	TRACE(("InterruptHandler()\n"));
 
	// start with the first (cached) irq condition
	iir = fCachedIIR;
	while ((iir & IIR_PENDING) == 0) { // 0 means yes
		status_t status;
		size_t bytesLeft;
		size_t readable = 0;
		size_t fifoavail = 1;
		size_t i;
		
		//DEBUG
//		for (int count = 0; ReadReg8(LSR) & LSR_DR; count++)
//			gTTYModule->ttyin(&fTTY, &fRover, ReadReg8(RBR));
 
		switch (iir & (IIR_IMASK | IIR_TO)) {
		case IIR_THRE:
			TRACE(("IIR_THRE\n"));
			// check how much fifo we can use
			//XXX: move to Init() ?
			if ((iir & IIR_FMASK) == IIR_FMASK)
				fifoavail = 16;
			if (iir & IIR_F64EN)
				fifoavail = 64;
			// we're not open... just discard the data
			if (!IsOpen())
				break;
			gTTYModule->tty_control(fDeviceTTYCookie, FIONREAD, &readable,
				sizeof(readable));
			TRACE("%s: FIONREAD: %d\n", __FUNCTION__, readable);
 
			if (readable == 0) {
				release_sem_etc(fDoneWrite, 1, B_DO_NOT_RESCHEDULE);
				// mask it until there's data again
				fCachedIER &= ~IER_THRE;
				break;
			}
 
			bytesLeft = MIN(fifoavail, sizeof(buffer));
			bytesLeft = MIN(bytesLeft, readable);
			TRACE("%s: left %d\n", __FUNCTION__, bytesLeft);
			status = gTTYModule->tty_read(fDeviceTTYCookie, buffer, &bytesLeft);
			TRACE("%s: tty_read: %d\n", __FUNCTION__, bytesLeft);
			if (status != B_OK) {
				dprintf(DRIVER_NAME ": irq: tty_read: %s\n", strerror(status));
				break;
			}
 
			for (i = 0; i < bytesLeft; i++) {
				WriteReg8(THB, buffer[i]);
			}
 
			break;
		case IIR_TO:
		case IIR_TO | IIR_RDA:
			// timeout: FALLTHROUGH
		case IIR_RDA:
			TRACE(("IIR_TO/RDA\n"));
			// while data is ready... and we have room for it, get it
			bytesLeft = sizeof(buffer);
			for (i = 0; i < bytesLeft && (ReadReg8(LSR) & LSR_DR); i++) {
				buffer[i] = ReadReg8(RBR);
			}
			// we're not open... just discard the data
			if (!IsOpen())
				break;
			// we shouldn't block here but it's < 256 bytes anyway
			status = gTTYModule->tty_write(fDeviceTTYCookie, buffer, &i);
			if (status != B_OK) {
				dprintf(DRIVER_NAME ": irq: tty_write: %s\n", strerror(status));
				break;
			}
			break;
		case IIR_RLS:
			TRACE(("IIR_RLS\n"));
			// ack
			lsr = ReadReg8(LSR);
			//XXX: handle this somehow
			break;
		case IIR_MS:
			TRACE(("IIR_MS\n"));
			// modem signals changed
			msr = ReadReg8(MSR);
			if (!IsOpen())
				break;
			if (msr & MSR_DDCD)
				SignalControlLineState(TTYHWDCD, msr & MSR_DCD);
			if (msr & MSR_DCTS)
				SignalControlLineState(TTYHWCTS, msr & MSR_CTS);
			if (msr & MSR_DDSR)
				SignalControlLineState(TTYHWDSR, msr & MSR_DSR);
			if (msr & MSR_TERI)
				SignalControlLineState(TTYHWRI, msr & MSR_RI);
			break;
		default:
			TRACE(("IIR_?\n"));
			// something happened
			break;
		}
		ret = B_HANDLED_INTERRUPT;
		TRACE(("IRQ:h\n"));
 
		// enough for now
		if (tries-- == 0)
			break;
 
		// check the next IRQ condition
		iir = ReadReg8(IIR);
	}
 
	atomic_add(&fPendingDPC, -1);
 
	// unmask IRQ
	WriteReg8(IER, fCachedIER);
 
	TRACE_FUNCRET("< IRQ:%d\n", ret);
	return ret;
}
 
 
status_t
SerialDevice::Open(uint32 flags)
{
	status_t status = B_OK;
 
	if (fDeviceOpen)
		return B_BUSY;
 
	if (fDeviceRemoved)
		return B_DEV_NOT_READY;
 
	fMasterTTY = gTTYModule->tty_create(pc_serial_service, true);
	if (fMasterTTY == NULL) {
		TRACE_ALWAYS("open: failed to init master tty\n");
		return B_NO_MEMORY;
	}
 
	fSlaveTTY = gTTYModule->tty_create(pc_serial_service, false);
	if (fSlaveTTY == NULL) {
		TRACE_ALWAYS("open: failed to init slave tty\n");
		gTTYModule->tty_destroy(fMasterTTY);
		return B_NO_MEMORY;
	}
 
	fSystemTTYCookie = gTTYModule->tty_create_cookie(fMasterTTY, fSlaveTTY,
		O_RDWR);
	if (fSystemTTYCookie == NULL) {
		TRACE_ALWAYS("open: failed to init system tty cookie\n");
		gTTYModule->tty_destroy(fMasterTTY);
		gTTYModule->tty_destroy(fSlaveTTY);
		return B_NO_MEMORY;
	}
 
	fDeviceTTYCookie = gTTYModule->tty_create_cookie(fSlaveTTY, fMasterTTY,
		O_RDWR);
	if (fDeviceTTYCookie == NULL) {
		TRACE_ALWAYS("open: failed to init device tty cookie\n");
		gTTYModule->tty_destroy_cookie(fSystemTTYCookie);
		gTTYModule->tty_destroy(fMasterTTY);
		gTTYModule->tty_destroy(fSlaveTTY);
		return B_NO_MEMORY;
	}
 
	ResetDevice();
 
	//XXX: we shouldn't have to do this!
	bool en = true;
	Service(fMasterTTY, TTYENABLE, &en, sizeof(en));
 
	if (status < B_OK) {
		TRACE_ALWAYS("open: failed to open tty\n");
		return status;
	}
 
	// set our config (will propagate to the slave config as well in SetModes()
	gTTYModule->tty_control(fSystemTTYCookie, TCSETA, &fTTYConfig,
		sizeof(termios));
 
#if 0
	fDeviceThread = spawn_kernel_thread(_DeviceThread, "usb_serial device thread",
		B_NORMAL_PRIORITY, this);
 
	if (fDeviceThread < B_OK) {
		TRACE_ALWAYS("open: failed to spawn kernel thread\n");
		return fDeviceThread;
	}
 
	resume_thread(fDeviceThread);
 
	fControlOut = CLS_LINE_DTR | CLS_LINE_RTS;
	SetControlLineState(fControlOut);
 
	status = gUSBModule->queue_interrupt(fControlPipe, fInterruptBuffer,
		fInterruptBufferSize, InterruptCallbackFunction, this);
	if (status < B_OK)
		TRACE_ALWAYS("failed to queue initial interrupt\n");
 
#endif
	fDeviceOpen = true;
	return B_OK;
}
 
 
status_t
SerialDevice::Read(char *buffer, size_t *numBytes)
{
	if (fDeviceRemoved) {
		*numBytes = 0;
		return B_DEV_NOT_READY;
	}
 
	status_t status;
 
	status = gTTYModule->tty_read(fSystemTTYCookie, buffer, numBytes);
 
	return status;
}
 
 
status_t
SerialDevice::Write(const char *buffer, size_t *numBytes)
{
	TRACE("%s(,&%d)\n", __FUNCTION__, *numBytes);
	if (fDeviceRemoved) {
		*numBytes = 0;
		return B_DEV_NOT_READY;
	}
 
	status_t status;
	size_t bytesLeft = *numBytes;
	*numBytes = 0;
 
	while (bytesLeft > 0) {
		size_t length = MIN(bytesLeft, 256);
			// TODO: This is an ugly hack; We use a small buffer size so that
			// we don't overrun the tty line buffer and cause it to block. While
			// that isn't a problem, we shouldn't just hardcode the value here.
 
		TRACE("%s: tty_write(,&%d)\n", __FUNCTION__, length);
		status = gTTYModule->tty_write(fSystemTTYCookie, buffer,
			&length);
		if (status != B_OK) {
			TRACE_ALWAYS("failed to write to tty: %s\n", strerror(status));
			return status;
		}
 
		buffer += length;
		*numBytes += length;
		bytesLeft -= length;
 
		// XXX: WTF: this ought to be done by the tty module calling service_func!
		// enable irqs
		Service(fMasterTTY, TTYOSTART, NULL, 0);
	}
 
	status = acquire_sem_etc(fDoneWrite, 1, B_CAN_INTERRUPT, 0);
	if (status != B_OK) {
		TRACE_ALWAYS("write: failed to get write done sem "
				"0x%08x\n", status);
		return status;
	}
 
 
	if (*numBytes > 0)
		return B_OK;
 
	return B_ERROR;
}
 
 
status_t
SerialDevice::Control(uint32 op, void *arg, size_t length)
{
	status_t status = B_OK;
 
	if (fDeviceRemoved)
		return B_DEV_NOT_READY;
 
	status = gTTYModule->tty_control(fSystemTTYCookie, op, arg, length);
 
	return status;
}
 
 
status_t
SerialDevice::Select(uint8 event, uint32 ref, selectsync *sync)
{
	if (fDeviceRemoved)
		return B_DEV_NOT_READY;
 
	return gTTYModule->tty_select(fSystemTTYCookie, event, ref, sync);
}
 
 
status_t
SerialDevice::DeSelect(uint8 event, selectsync *sync)
{
	if (fDeviceRemoved)
		return B_DEV_NOT_READY;
 
	return gTTYModule->tty_deselect(fSystemTTYCookie, event, sync);
}
 
 
status_t
SerialDevice::Close()
{
	status_t status = B_OK;
 
	OnClose();
 
	if (!fDeviceRemoved) {
#if 0
		gUSBModule->cancel_queued_transfers(fReadPipe);
		gUSBModule->cancel_queued_transfers(fWritePipe);
		gUSBModule->cancel_queued_transfers(fControlPipe);
#endif
	}
 
	fDeviceOpen = false;
 
	gTTYModule->tty_close_cookie(fSystemTTYCookie);
	gTTYModule->tty_close_cookie(fDeviceTTYCookie);
 
	//XXX: we shouldn't have to do this!
	bool en = false;
	Service(fMasterTTY, TTYENABLE, &en, sizeof(en));
 
	return status;
}
 
 
status_t
SerialDevice::Free()
{
	status_t status = B_OK;
 
	// wait until currently executing DPC is done. In case another one
	// is run beyond this point it will just bail out on !IsOpen().
	//while (atomic_get(&fPendingDPC))
	//	snooze(1000);
 
	gTTYModule->tty_destroy_cookie(fSystemTTYCookie);
	gTTYModule->tty_destroy_cookie(fDeviceTTYCookie);
	fSystemTTYCookie = fDeviceTTYCookie = NULL;
 
	gTTYModule->tty_destroy(fMasterTTY);
	gTTYModule->tty_destroy(fSlaveTTY);
	fMasterTTY = fSlaveTTY = NULL;
 
	return status;
}
 
 
void
SerialDevice::Removed()
{
	if (fDeviceRemoved)
		return;
 
	// notifies us that the device was removed
	fDeviceRemoved = true;
 
	// we need to ensure that we do not use the device anymore
	fStopDeviceThread = true;
	fInputStopped = false;
#if 0
	gUSBModule->cancel_queued_transfers(fReadPipe);
	gUSBModule->cancel_queued_transfers(fWritePipe);
	gUSBModule->cancel_queued_transfers(fControlPipe);
#endif
 
	int32 result = B_OK;
	wait_for_thread(fDeviceThread, &result);
	fDeviceThread = -1;
}
 
 
status_t
SerialDevice::AddDevice(const serial_config_descriptor *config)
{
	// default implementation - does nothing
	return B_ERROR;
}
 
 
status_t
SerialDevice::ResetDevice()
{
	// default implementation - does nothing
	return B_OK;
}
 
 
#if 0
status_t
SerialDevice::SetLineCoding(usb_serial_line_coding *coding)
{
	// default implementation - does nothing
	return B_OK;
}
#endif
 
status_t
SerialDevice::SignalControlLineState(int line, bool enable)
{
	gTTYModule->tty_hardware_signal(fSystemTTYCookie, line, enable);
 
	return B_OK;
}
 
 
void
SerialDevice::OnRead(char **buffer, size_t *numBytes)
{
	// default implementation - does nothing
}
 
 
void
SerialDevice::OnWrite(const char *buffer, size_t *numBytes, size_t *packetBytes)
{
	// default implementation - does nothing
}
 
 
void
SerialDevice::OnClose()
{
	// default implementation - does nothing
}
 
 
int32
SerialDevice::_DeviceThread(void *data)
{
#if 0
	SerialDevice *device = (SerialDevice *)data;
 
	while (!device->fStopDeviceThread) {
		status_t status = gUSBModule->queue_bulk(device->fReadPipe,
			device->fReadBuffer, device->fReadBufferSize,
			device->ReadCallbackFunction, data);
		if (status < B_OK) {
			TRACE_ALWAYS("device thread: queueing failed with error: 0x%08x\n", status);
			break;
		}
 
		status = acquire_sem_etc(device->fDoneRead, 1, B_CAN_INTERRUPT, 0);
		if (status < B_OK) {
			TRACE_ALWAYS("device thread: failed to get read done sem 0x%08x\n", status);
			break;
		}
 
		if (device->fStatusRead != B_OK) {
			TRACE("device thread: device status error 0x%08x\n",
				device->fStatusRead);
			if (gUSBModule->clear_feature(device->fReadPipe,
				USB_FEATURE_ENDPOINT_HALT) != B_OK) {
				TRACE_ALWAYS("device thread: failed to clear halt feature\n");
				break;
			}
		}
 
		char *buffer = device->fReadBuffer;
		size_t readLength = device->fActualLengthRead;
		device->OnRead(&buffer, &readLength);
		if (readLength == 0)
			continue;
 
		ddrover *ddr = gTTYModule->ddrstart(NULL);
		if (!ddr) {
			TRACE_ALWAYS("device thread: ddrstart problem\n");
			return B_NO_MEMORY;
		}
 
		while (device->fInputStopped)
			snooze(100);
 
		gTTYModule->ttyilock(&device->fTTY, ddr, true);
		for (size_t i = 0; i < readLength; i++)
			gTTYModule->ttyin(&device->fTTY, ddr, buffer[i]);
 
		gTTYModule->ttyilock(&device->fTTY, ddr, false);
		gTTYModule->ddrdone(ddr);
	}
 
#endif
	return B_OK;
}
 
 
status_t
SerialDevice::_WriteToDevice()
{
	char *buffer = &fWriteBuffer[fWriteBufferIn];
	size_t bytesLeft = DEF_BUFFER_SIZE - atomic_get(&fWriteBufferAvail);
	bytesLeft = MIN(bytesLeft, DEF_BUFFER_SIZE - fWriteBufferIn);
	TRACE("%s: in %d left %d\n", __FUNCTION__, fWriteBufferIn, bytesLeft);
	status_t status = gTTYModule->tty_read(fDeviceTTYCookie, buffer,
		&bytesLeft);
	TRACE("%s: tty_read: %d\n", __FUNCTION__, bytesLeft);
	if (status != B_OK) {
		TRACE_ALWAYS("write to device: failed to read from TTY: %s\n",
			strerror(status));
		return status;
	}
	fWriteBufferIn += bytesLeft;
	fWriteBufferIn %= DEF_BUFFER_SIZE;
	atomic_add(&fWriteBufferAvail, bytesLeft);
 
	// XXX: WTF: this ought to be done by the tty module calling service_func!
	// enable irqs
	Service(fMasterTTY, TTYOSTART, NULL, 0);
 
	status = acquire_sem_etc(fWriteBufferSem, 1, B_CAN_INTERRUPT, 0);
	if (status != B_OK) {
		TRACE_ALWAYS("write to device: failed to acquire sem: %s\n",
			strerror(status));
		return status;
	}
	return B_OK;
}
 
 
void
SerialDevice::ReadCallbackFunction(void *cookie, int32 status, void *data,
	uint32 actualLength)
{
	TRACE_FUNCALLS("read callback: cookie: 0x%08x status: 0x%08x data: 0x%08x len: %lu\n",
		cookie, status, data, actualLength);
 
	SerialDevice *device = (SerialDevice *)cookie;
	device->fActualLengthRead = actualLength;
	device->fStatusRead = status;
	release_sem_etc(device->fDoneRead, 1, B_DO_NOT_RESCHEDULE);
}
 
 
void
SerialDevice::WriteCallbackFunction(void *cookie, int32 status, void *data,
	uint32 actualLength)
{
	TRACE_FUNCALLS("write callback: cookie: 0x%08x status: 0x%08x data: 0x%08x len: %lu\n",
		cookie, status, data, actualLength);
 
	SerialDevice *device = (SerialDevice *)cookie;
	device->fActualLengthWrite = actualLength;
	device->fStatusWrite = status;
	release_sem_etc(device->fDoneWrite, 1, B_DO_NOT_RESCHEDULE);
}
 
 
void
SerialDevice::InterruptCallbackFunction(void *cookie, int32 status,
	void *data, uint32 actualLength)
{
	TRACE_FUNCALLS("interrupt callback: cookie: 0x%08x status: 0x%08x data: 0x%08x len: %lu\n",
		cookie, status, data, actualLength);
 
	SerialDevice *device = (SerialDevice *)cookie;
	device->fActualLengthInterrupt = actualLength;
	device->fStatusInterrupt = status;
 
	// ToDo: maybe handle those somehow?
 
	if (status == B_OK && !device->fDeviceRemoved) {
#if 0
		status = gUSBModule->queue_interrupt(device->fControlPipe,
			device->fInterruptBuffer, device->fInterruptBufferSize,
			device->InterruptCallbackFunction, device);
#endif
	}
}
 
 
 
#if 0
SerialDevice *
SerialDevice::MakeDevice(usb_device device, uint16 vendorID,
	uint16 productID)
{
	const char *description = NULL;
 
	switch (vendorID) {
		case VENDOR_IODATA:
		case VENDOR_ATEN:
		case VENDOR_TDK:
		case VENDOR_RATOC:
		case VENDOR_PROLIFIC:
		case VENDOR_ELECOM:
		case VENDOR_SOURCENEXT:
		case VENDOR_HAL:
		{
			switch (productID) {
				case PRODUCT_PROLIFIC_RSAQ2: description = "PL2303 Serial adapter (IODATA USB-RSAQ2)"; break;
				case PRODUCT_IODATA_USBRSAQ: description = "I/O Data USB serial adapter USB-RSAQ1"; break;
				case PRODUCT_ATEN_UC232A: description = "Aten Serial adapter"; break;
				case PRODUCT_TDK_UHA6400: description = "TDK USB-PHS Adapter UHA6400"; break;
				case PRODUCT_RATOC_REXUSB60: description = "Ratoc USB serial adapter REX-USB60"; break;
				case PRODUCT_PROLIFIC_PL2303: description = "PL2303 Serial adapter (ATEN/IOGEAR UC232A)"; break;
				case PRODUCT_ELECOM_UCSGT: description = "Elecom UC-SGT"; break;
				case PRODUCT_SOURCENEXT_KEIKAI8: description = "SOURCENEXT KeikaiDenwa 8"; break;
				case PRODUCT_SOURCENEXT_KEIKAI8_CHG: description = "SOURCENEXT KeikaiDenwa 8 with charger"; break;
				case PRODUCT_HAL_IMR001: description = "HAL Corporation Crossam2+USB"; break;
			}
 
			if (!description)
				break;
 
			return new(std::nothrow) ProlificDevice(device, vendorID, productID, description);
		}
 
		case VENDOR_FTDI:
		{
			switch (productID) {
				case PRODUCT_FTDI_8U100AX: description = "FTDI 8U100AX serial converter"; break;
				case PRODUCT_FTDI_8U232AM: description = "FTDI 8U232AM serial converter"; break;
			}
 
			if (!description)
				break;
 
			return new(std::nothrow) FTDIDevice(device, vendorID, productID, description);
		}
 
		case VENDOR_PALM:
		case VENDOR_KLSI:
		{
			switch (productID) {
				case PRODUCT_PALM_CONNECT: description = "PalmConnect RS232"; break;
				case PRODUCT_KLSI_KL5KUSB105D: description = "KLSI KL5KUSB105D"; break;
			}
 
			if (!description)
				break;
 
			return new(std::nothrow) KLSIDevice(device, vendorID, productID, description);
		}
	}
 
	return new(std::nothrow) ACMDevice(device, vendorID, productID, "CDC ACM compatible device");
}
#endif
 
 
uint8
SerialDevice::ReadReg8(int reg)
{
	uint8 ret;
	switch (fBus) {
	case B_ISA_BUS:
		ret = gISAModule->read_io_8(IOBase() + reg);
		break;
	case B_PCI_BUS:
		ret = gPCIModule->read_io_8(IOBase() + reg);
		break;
	default:
		TRACE_ALWAYS("%s: unknown bus!\n", __FUNCTION__);
		ret = 0;
	//XXX:pcmcia ?
	}
	TRACE/*_ALWAYS*/("RR8(%d) = %d [%02x]\n", reg, ret, ret);
	//spin(1000);
	return ret;
}
 
void
SerialDevice::WriteReg8(int reg, uint8 value)
{
//	TRACE_ALWAYS("WR8(0x%04x+%d, %d [0x%x])\n", IOBase(), reg, value, value);
	TRACE/*_ALWAYS*/("WR8(%d, %d [0x%x])\n", reg, value, value);
	switch (fBus) {
	case B_ISA_BUS:
		gISAModule->write_io_8(IOBase() + reg, value);
		break;
	case B_PCI_BUS:
		gPCIModule->write_io_8(IOBase() + reg, value);
		break;
	default:
		TRACE_ALWAYS("%s: unknown bus!\n", __FUNCTION__);
	//XXX:pcmcia ?
	}
	//spin(10000);
}
 
 
void
SerialDevice::OrReg8(int reg, uint8 value)
{
	WriteReg8(reg, ReadReg8(reg) | value);
}
 
 
void
SerialDevice::AndReg8(int reg, uint8 value)
{
	WriteReg8(reg, ReadReg8(reg) & value);
}
 
 
void
SerialDevice::MaskReg8(int reg, uint8 value)
{
	WriteReg8(reg, ReadReg8(reg) & ~value);
}

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fActualLengthRead, fStatusRead, fActualLengthWrite, fStatusWrite, fActualLengthInterrupt, fStatusInterrupt.

V547 Expression 'status < ((int) 0)' is always false.