patch-2.4.21 linux-2.4.21/drivers/usb/auerchar.c

Next file: linux-2.4.21/drivers/usb/auerchar.h
Previous file: linux-2.4.21/drivers/usb/auerchain.h
Back to the patch index
Back to the overall index

diff -urN linux-2.4.20/drivers/usb/auerchar.c linux-2.4.21/drivers/usb/auerchar.c
@@ -0,0 +1,615 @@
+/*****************************************************************************/
+/*
+ *      auerchar.c  --  Auerswald PBX/System Telephone character interface.
+ *
+ *      Copyright (C) 2002  Wolfgang Mües (wolfgang@iksw-muees.de)
+ *
+ *      Very much code of this driver is borrowed from dabusb.c (Deti Fliegl)
+ *      and from the USB Skeleton driver (Greg Kroah-Hartman). Thank you.
+ *
+ *      This program is free software; you can redistribute it and/or modify
+ *      it under the terms of the GNU General Public License as published by
+ *      the Free Software Foundation; either version 2 of the License, or
+ *      (at your option) any later version.
+ *
+ *      This program is distributed in the hope that it will be useful,
+ *      but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *      GNU General Public License for more details.
+ *
+ *      You should have received a copy of the GNU General Public License
+ *      along with this program; if not, write to the Free Software
+ *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+ /*****************************************************************************/
+
+#undef DEBUG			/* include debug macros until it's done */
+#include <linux/usb.h>
+#include "auerchar.h"
+#include "auermain.h"
+#include <linux/slab.h>
+#include <asm/uaccess.h>	/* user area access functions */
+
+/*-------------------------------------------------------------------*/
+
+/* wake up waiting readers */
+static void auerchar_disconnect(struct auerscon *scp)
+{
+	struct auerchar *ccp =((struct auerchar *) ((char *) (scp) - (unsigned long) (&((struct auerchar *) 0)->scontext)));
+	dbg("auerchar_disconnect called");
+	ccp->removed = 1;
+	wake_up(&ccp->readwait);
+}
+
+
+/* dispatch a read paket to a waiting character device */
+static void auerchar_ctrlread_dispatch(struct auerscon *scp,
+				       struct auerbuf *bp)
+{
+	unsigned long flags;
+	struct auerchar *ccp;
+	struct auerbuf *newbp = NULL;
+	char *charp;
+	dbg("auerchar_ctrlread_dispatch called");
+	ccp =((struct auerchar *) ((char *) (scp) - (unsigned long)(&((struct auerchar *) 0)->scontext)));
+
+	/* get a read buffer from character device context */
+	newbp = auerbuf_getbuf(&ccp->bufctl);
+	if (!newbp) {
+		dbg("No read buffer available, discard paket!");
+		return;		/* no buffer, no dispatch */
+	}
+
+	/* copy information to new buffer element
+	   (all buffers have the same length) */
+	charp = newbp->bufp;
+	newbp->bufp = bp->bufp;
+	bp->bufp = charp;
+	newbp->len = bp->len;
+
+	/* insert new buffer in read list */
+	spin_lock_irqsave(&ccp->bufctl.lock, flags);
+	list_add_tail(&newbp->buff_list, &ccp->bufctl.rec_buff_list);
+	spin_unlock_irqrestore(&ccp->bufctl.lock, flags);
+	dbg("read buffer appended to rec_list");
+
+	/* wake up pending synchronous reads */
+	wake_up(&ccp->readwait);
+}
+
+
+/* Delete an auerswald character context */
+void auerchar_delete(struct auerchar *ccp)
+{
+	dbg("auerchar_delete");
+	if (ccp == NULL)
+		return;
+
+	/* wake up pending synchronous reads */
+	ccp->removed = 1;
+	wake_up(&ccp->readwait);
+
+	/* remove the read buffer */
+	if (ccp->readbuf) {
+		auerbuf_releasebuf(ccp->readbuf);
+		ccp->readbuf = NULL;
+	}
+
+	/* remove the character buffers */
+	auerbuf_free_buffers(&ccp->bufctl);
+
+	/* release the memory */
+	kfree(ccp);
+}
+
+
+/* --------------------------------------------------------------------- */
+/* Char device functions                                                 */
+
+/* Open a new character device */
+int auerchar_open(struct inode *inode, struct file *file)
+{
+	int dtindex = MINOR(inode->i_rdev) - AUER_MINOR_BASE;
+	struct auerswald *cp = NULL;
+	struct auerchar *ccp = NULL;
+	int ret;
+
+	/* minor number in range? */
+	if ((dtindex < 0) || (dtindex >= AUER_MAX_DEVICES)) {
+		return -ENODEV;
+	}
+	/* usb device available? */
+	if (down_interruptible(&auerdev_table_mutex)) {
+		return -ERESTARTSYS;
+	}
+	cp = auerdev_table[dtindex];
+	if (cp == NULL) {
+		up(&auerdev_table_mutex);
+		return -ENODEV;
+	}
+	if (down_interruptible(&cp->mutex)) {
+		up(&auerdev_table_mutex);
+		return -ERESTARTSYS;
+	}
+	up(&auerdev_table_mutex);
+
+	/* we have access to the device. Now lets allocate memory */
+	ccp = (struct auerchar *) kmalloc(sizeof(struct auerchar), GFP_KERNEL);
+	if (ccp == NULL) {
+		err("out of memory");
+		ret = -ENOMEM;
+		goto ofail;
+	}
+
+	/* Initialize device descriptor */
+	memset(ccp, 0, sizeof(struct auerchar));
+	init_MUTEX(&ccp->mutex);
+	init_MUTEX(&ccp->readmutex);
+	auerbuf_init(&ccp->bufctl);
+	ccp->scontext.id = AUH_UNASSIGNED;
+	ccp->scontext.dispatch = auerchar_ctrlread_dispatch;
+	ccp->scontext.disconnect = auerchar_disconnect;
+	init_waitqueue_head(&ccp->readwait);
+
+	ret =
+	    auerbuf_setup(&ccp->bufctl, AU_RBUFFERS,
+			  cp->maxControlLength + AUH_SIZE);
+	if (ret) {
+		goto ofail;
+	}
+
+	cp->open_count++;
+	ccp->auerdev = cp;
+	dbg("open %s as /dev/usb/%s", cp->dev_desc, cp->name);
+	up(&cp->mutex);
+
+	/* file IO stuff */
+	file->f_pos = 0;
+	file->private_data = ccp;
+	return 0;
+
+	/* Error exit */
+      ofail:up(&cp->mutex);
+	auerchar_delete(ccp);
+	return ret;
+}
+
+
+/* IOCTL functions */
+int auerchar_ioctl(struct inode *inode, struct file *file,
+		   unsigned int cmd, unsigned long arg)
+{
+	struct auerchar *ccp = (struct auerchar *) file->private_data;
+	int ret = 0;
+	struct audevinfo devinfo;
+	struct auerswald *cp = NULL;
+	unsigned int u;
+	dbg("ioctl");
+
+	/* get the mutexes */
+	if (down_interruptible(&ccp->mutex)) {
+		return -ERESTARTSYS;
+	}
+	cp = ccp->auerdev;
+	if (!cp) {
+		up(&ccp->mutex);
+		return -ENODEV;
+	}
+	if (down_interruptible(&cp->mutex)) {
+		up(&ccp->mutex);
+		return -ERESTARTSYS;
+	}
+
+	/* Check for removal */
+	if (!cp->usbdev) {
+		up(&cp->mutex);
+		up(&ccp->mutex);
+		return -ENODEV;
+	}
+
+	switch (cmd) {
+
+		/* return != 0 if Transmitt channel ready to send */
+	case IOCTL_AU_TXREADY:
+		dbg("IOCTL_AU_TXREADY");
+		u = ccp->auerdev && (ccp->scontext.id != AUH_UNASSIGNED)
+		    && !list_empty(&cp->bufctl.free_buff_list);
+		ret = put_user(u, (unsigned int *) arg);
+		break;
+
+		/* return != 0 if connected to a service channel */
+	case IOCTL_AU_CONNECT:
+		dbg("IOCTL_AU_CONNECT");
+		u = (ccp->scontext.id != AUH_UNASSIGNED);
+		ret = put_user(u, (unsigned int *) arg);
+		break;
+
+		/* return != 0 if Receive Data available */
+	case IOCTL_AU_RXAVAIL:
+		dbg("IOCTL_AU_RXAVAIL");
+		if (ccp->scontext.id == AUH_UNASSIGNED) {
+			ret = -EIO;
+			break;
+		}
+		u = 0;		/* no data */
+		if (ccp->readbuf) {
+			int restlen = ccp->readbuf->len - ccp->readoffset;
+			if (restlen > 0)
+				u = 1;
+		}
+		if (!u) {
+			if (!list_empty(&ccp->bufctl.rec_buff_list)) {
+				u = 1;
+			}
+		}
+		ret = put_user(u, (unsigned int *) arg);
+		break;
+
+		/* return the max. buffer length for the device */
+	case IOCTL_AU_BUFLEN:
+		dbg("IOCTL_AU_BUFLEN");
+		u = cp->maxControlLength;
+		ret = put_user(u, (unsigned int *) arg);
+		break;
+
+		/* requesting a service channel */
+	case IOCTL_AU_SERVREQ:
+		dbg("IOCTL_AU_SERVREQ");
+		/* requesting a service means: release the previous one first */
+		auerswald_removeservice(cp, &ccp->scontext);
+		/* get the channel number */
+		ret = get_user(u, (unsigned int *) arg);
+		if (ret) {
+			break;
+		}
+		if ((u < AUH_FIRSTUSERCH) || (u >= AUH_TYPESIZE)) {
+			ret = -EIO;
+			break;
+		}
+		dbg("auerchar service request parameters are ok");
+		ccp->scontext.id = u;
+
+		/* request the service now */
+		ret = auerswald_addservice(cp, &ccp->scontext);
+		if (ret) {
+			/* no: revert service entry */
+			ccp->scontext.id = AUH_UNASSIGNED;
+		}
+		break;
+
+		/* get a string descriptor for the device */
+	case IOCTL_AU_DEVINFO:
+		dbg("IOCTL_AU_DEVINFO");
+		if (copy_from_user
+		    (&devinfo, (void *) arg, sizeof(struct audevinfo))) {
+			ret = -EFAULT;
+			break;
+		}
+		u = strlen(cp->dev_desc) + 1;
+		if (u > devinfo.bsize) {
+			u = devinfo.bsize;
+		}
+		ret = copy_to_user(devinfo.buf, cp->dev_desc, u);
+		break;
+
+		/* get the max. string descriptor length */
+	case IOCTL_AU_SLEN:
+		dbg("IOCTL_AU_SLEN");
+		u = AUSI_DLEN;
+		ret = put_user(u, (unsigned int *) arg);
+		break;
+
+	default:
+		dbg("IOCTL_AU_UNKNOWN");
+		ret = -ENOIOCTLCMD;
+		break;
+	}
+	/* release the mutexes */
+	up(&cp->mutex);
+	up(&ccp->mutex);
+	return ret;
+}
+
+
+/* Seek is not supported */
+loff_t auerchar_llseek(struct file * file, loff_t offset, int origin)
+{
+	dbg("auerchar_seek");
+	return -ESPIPE;
+}
+
+
+/* Read data from the device */
+ssize_t auerchar_read(struct file * file, char *buf, size_t count,
+		      loff_t * ppos)
+{
+	unsigned long flags;
+	struct auerchar *ccp = (struct auerchar *) file->private_data;
+	struct auerbuf *bp = NULL;
+	wait_queue_t wait;
+
+	dbg("auerchar_read");
+
+	/* Error checking */
+	if (!ccp)
+		return -EIO;
+	if (*ppos)
+		return -ESPIPE;
+	if (count == 0)
+		return 0;
+
+	/* get the mutex */
+	if (down_interruptible(&ccp->mutex))
+		return -ERESTARTSYS;
+
+	/* Can we expect to read something? */
+	if (ccp->scontext.id == AUH_UNASSIGNED) {
+		up(&ccp->mutex);
+		return -EIO;
+	}
+
+	/* only one reader per device allowed */
+	if (down_interruptible(&ccp->readmutex)) {
+		up(&ccp->mutex);
+		return -ERESTARTSYS;
+	}
+
+	/* read data from readbuf, if available */
+      doreadbuf:
+	bp = ccp->readbuf;
+	if (bp) {
+		/* read the maximum bytes */
+		int restlen = bp->len - ccp->readoffset;
+		if (restlen < 0)
+			restlen = 0;
+		if (count > restlen)
+			count = restlen;
+		if (count) {
+			if (copy_to_user
+			    (buf, bp->bufp + ccp->readoffset, count)) {
+				dbg("auerswald_read: copy_to_user failed");
+				up(&ccp->readmutex);
+				up(&ccp->mutex);
+				return -EFAULT;
+			}
+		}
+		/* advance the read offset */
+		ccp->readoffset += count;
+		restlen -= count;
+		// reuse the read buffer
+		if (restlen <= 0) {
+			auerbuf_releasebuf(bp);
+			ccp->readbuf = NULL;
+		}
+		/* return with number of bytes read */
+		if (count) {
+			up(&ccp->readmutex);
+			up(&ccp->mutex);
+			return count;
+		}
+	}
+
+	/* a read buffer is not available. Try to get the next data block. */
+      doreadlist:
+	/* Preparing for sleep */
+	init_waitqueue_entry(&wait, current);
+	set_current_state(TASK_INTERRUPTIBLE);
+	add_wait_queue(&ccp->readwait, &wait);
+
+	bp = NULL;
+	spin_lock_irqsave(&ccp->bufctl.lock, flags);
+	if (!list_empty(&ccp->bufctl.rec_buff_list)) {
+		/* yes: get the entry */
+		struct list_head *tmp = ccp->bufctl.rec_buff_list.next;
+		list_del(tmp);
+		bp = list_entry(tmp, struct auerbuf, buff_list);
+	}
+	spin_unlock_irqrestore(&ccp->bufctl.lock, flags);
+
+	/* have we got data? */
+	if (bp) {
+		ccp->readbuf = bp;
+		ccp->readoffset = AUH_SIZE;	/* for headerbyte */
+		set_current_state(TASK_RUNNING);
+		remove_wait_queue(&ccp->readwait, &wait);
+		goto doreadbuf;	/* now we can read! */
+	}
+
+	/* no data available. Should we wait? */
+	if (file->f_flags & O_NONBLOCK) {
+		dbg("No read buffer available, returning -EAGAIN");
+		set_current_state(TASK_RUNNING);
+		remove_wait_queue(&ccp->readwait, &wait);
+		up(&ccp->readmutex);
+		up(&ccp->mutex);
+		return -EAGAIN;	/* nonblocking, no data available */
+	}
+
+	/* yes, we should wait! */
+	up(&ccp->mutex);	/* allow other operations while we wait */
+	schedule();
+	remove_wait_queue(&ccp->readwait, &wait);
+	if (signal_pending(current)) {
+		/* waked up by a signal */
+		up(&ccp->readmutex);
+		return -ERESTARTSYS;
+	}
+
+	/* Anything left to read? */
+	if ((ccp->scontext.id == AUH_UNASSIGNED) || ccp->removed) {
+		up(&ccp->readmutex);
+		return -EIO;
+	}
+
+	if (down_interruptible(&ccp->mutex)) {
+		up(&ccp->readmutex);
+		return -ERESTARTSYS;
+	}
+
+	/* try to read the incomming data again */
+	goto doreadlist;
+}
+
+
+/* Write a data block into the right service channel of the device */
+ssize_t auerchar_write(struct file *file, const char *buf, size_t len,
+		       loff_t * ppos)
+{
+	struct auerchar *ccp = (struct auerchar *) file->private_data;
+	struct auerswald *cp = NULL;
+	struct auerbuf *bp;
+	int ret;
+	wait_queue_t wait;
+
+	dbg("auerchar_write %d bytes", len);
+
+	/* Error checking */
+	if (!ccp)
+		return -EIO;
+	if (*ppos)
+		return -ESPIPE;
+	if (len == 0)
+		return 0;
+
+      write_again:
+	/* get the mutex */
+	if (down_interruptible(&ccp->mutex))
+		return -ERESTARTSYS;
+
+	/* Can we expect to write something? */
+	if (ccp->scontext.id == AUH_UNASSIGNED) {
+		up(&ccp->mutex);
+		return -EIO;
+	}
+
+	cp = ccp->auerdev;
+	if (!cp) {
+		up(&ccp->mutex);
+		return -ERESTARTSYS;
+	}
+	if (down_interruptible(&cp->mutex)) {
+		up(&ccp->mutex);
+		return -ERESTARTSYS;
+	}
+	if (!cp->usbdev) {
+		up(&cp->mutex);
+		up(&ccp->mutex);
+		return -EIO;
+	}
+	/* Prepare for sleep */
+	init_waitqueue_entry(&wait, current);
+	set_current_state(TASK_INTERRUPTIBLE);
+	add_wait_queue(&cp->bufferwait, &wait);
+
+	/* Try to get a buffer from the device pool.
+	   We can't use a buffer from ccp->bufctl because the write
+	   command will last beond a release() */
+	bp = auerbuf_getbuf(&cp->bufctl);
+	/* are there any buffers left? */
+	if (!bp) {
+		up(&cp->mutex);
+		up(&ccp->mutex);
+
+		/* NONBLOCK: don't wait */
+		if (file->f_flags & O_NONBLOCK) {
+			set_current_state(TASK_RUNNING);
+			remove_wait_queue(&cp->bufferwait, &wait);
+			return -EAGAIN;
+		}
+
+		/* BLOCKING: wait */
+		schedule();
+		remove_wait_queue(&cp->bufferwait, &wait);
+		if (signal_pending(current)) {
+			/* waked up by a signal */
+			return -ERESTARTSYS;
+		}
+		goto write_again;
+	} else {
+		set_current_state(TASK_RUNNING);
+		remove_wait_queue(&cp->bufferwait, &wait);
+	}
+
+	/* protect against too big write requests */
+	if (len > cp->maxControlLength)
+		len = cp->maxControlLength;
+
+	/* Fill the buffer */
+	if (copy_from_user(bp->bufp + AUH_SIZE, buf, len)) {
+		dbg("copy_from_user failed");
+		auerbuf_releasebuf(bp);
+		/* Wake up all processes waiting for a buffer */
+		wake_up(&cp->bufferwait);
+		up(&cp->mutex);
+		up(&ccp->mutex);
+		return -EIO;
+	}
+
+	/* set the header byte */
+	*(bp->bufp) = ccp->scontext.id | AUH_DIRECT | AUH_UNSPLIT;
+
+	/* Set the transfer Parameters */
+	bp->len = len + AUH_SIZE;
+	bp->dr->bRequestType = AUT_WREQ;
+	bp->dr->bRequest = AUV_WBLOCK;
+	bp->dr->wValue = cpu_to_le16(0);
+	bp->dr->wIndex =
+	    cpu_to_le16(ccp->scontext.id | AUH_DIRECT | AUH_UNSPLIT);
+	bp->dr->wLength = cpu_to_le16(len + AUH_SIZE);
+	FILL_CONTROL_URB(bp->urbp, cp->usbdev,
+			 usb_sndctrlpipe(cp->usbdev, 0),
+			 (unsigned char *) bp->dr, bp->bufp,
+			 len + AUH_SIZE, auerchar_ctrlwrite_complete, bp);
+	/* up we go */
+	ret = auerchain_submit_urb(&cp->controlchain, bp->urbp);
+	up(&cp->mutex);
+	if (ret) {
+		dbg("auerchar_write: nonzero result of auerchain_submit_urb %d", ret);
+		auerbuf_releasebuf(bp);
+		/* Wake up all processes waiting for a buffer */
+		wake_up(&cp->bufferwait);
+		up(&ccp->mutex);
+		return -EIO;
+	} else {
+		dbg("auerchar_write: Write OK");
+		up(&ccp->mutex);
+		return len;
+	}
+}
+
+
+/* Close a character device */
+int auerchar_release(struct inode *inode, struct file *file)
+{
+	struct auerchar *ccp = (struct auerchar *) file->private_data;
+	struct auerswald *cp;
+	dbg("release");
+
+	/* get the mutexes */
+	if (down_interruptible(&ccp->mutex)) {
+		return -ERESTARTSYS;
+	}
+	cp = ccp->auerdev;
+	if (cp) {
+		if (down_interruptible(&cp->mutex)) {
+			up(&ccp->mutex);
+			return -ERESTARTSYS;
+		}
+		/* remove an open service */
+		auerswald_removeservice(cp, &ccp->scontext);
+		/* detach from device */
+		if ((--cp->open_count <= 0) && (cp->usbdev == NULL)) {
+			/* usb device waits for removal */
+			up(&cp->mutex);
+			auerswald_delete(cp);
+		} else {
+			up(&cp->mutex);
+		}
+		cp = NULL;
+		ccp->auerdev = NULL;
+	}
+	up(&ccp->mutex);
+	auerchar_delete(ccp);
+
+	return 0;
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)