patch-2.4.19 linux-2.4.19/drivers/usb/brlvger.c
Next file: linux-2.4.19/drivers/usb/catc.c
Previous file: linux-2.4.19/drivers/usb/auerswald.c
Back to the patch index
Back to the overall index
- Lines: 1035
- Date:
Fri Aug 2 17:39:44 2002
- Orig file:
linux-2.4.18/drivers/usb/brlvger.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -urN linux-2.4.18/drivers/usb/brlvger.c linux-2.4.19/drivers/usb/brlvger.c
@@ -0,0 +1,1034 @@
+/*
+ * Tieman Voyager braille display USB driver.
+ *
+ * Copyright 2001-2002 Stephane Dalton <sdalton@videotron.ca>
+ * and Stéphane Doyon <s.doyon@videotron.ca>
+ * Maintained by Stéphane Doyon <s.doyon@videotron.ca>.
+ */
+/*
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+/* History:
+ * 0.8 April 2002: Integration into the kernel tree.
+ * 0.7 October 2001: First public release as a module, distributed with
+ * the BRLTTY package (beta versions around 2.99y).
+ */
+
+#define DRIVER_VERSION "v0.8"
+#define DATE "April 2002"
+#define DRIVER_AUTHOR \
+ "Stephane Dalton <sdalton@videotron.ca> " \
+ "and Stéphane Doyon <s.doyon@videotron.ca>"
+#define DRIVER_DESC "Tieman Voyager braille display USB driver for Linux 2.4"
+#define DRIVER_SHORTDESC "Voyager"
+
+#define BANNER \
+ KERN_INFO DRIVER_SHORTDESC " " DRIVER_VERSION " (" DATE ")\n" \
+ KERN_INFO " by " DRIVER_AUTHOR "\n"
+
+static const char longbanner[] = {
+ DRIVER_DESC ", " DRIVER_VERSION " (" DATE "), by " DRIVER_AUTHOR
+};
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+#include <linux/poll.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/brlvger.h>
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL");
+
+/* Module parameters */
+
+static int debug = 1;
+MODULE_PARM(debug, "i");
+MODULE_PARM_DESC(debug, "Debug level, 0-3");
+
+static int write_repeats = 2;
+MODULE_PARM(write_repeats, "i");
+MODULE_PARM_DESC(write_repeats, "Hack: repetitions for command to "
+ "display braille pattern");
+ /* to get rid of weird extra dots (perhaps only on
+ early hardware versions?) */
+
+static int stall_tries = 3;
+MODULE_PARM(stall_tries, "i");
+MODULE_PARM_DESC(stall_tries, "Hack: retransmits of stalled USB "
+ "control messages");
+ /* broken early hardware versions? */
+
+#define BRLVGER_RAW_VOLTAGE 89
+/* from 0->300V to 255->200V, we are told 265V is normal operating voltage,
+ but we don't know the scale. Assuming it is linear. */
+static int raw_voltage = BRLVGER_RAW_VOLTAGE;
+MODULE_PARM(raw_voltage, "i");
+MODULE_PARM_DESC(raw_voltage, "Parameter for the call to SET_DISPLAY_VOLTAGE");
+
+
+/* protocol and display type defines */
+#define MAX_BRLVGER_CELLS 72
+#define MAX_INTERRUPT_DATA 8
+/* control message request types */
+#define BRLVGER_READ_REQ 0xC2
+#define BRLVGER_WRITE_REQ 0x42
+/* control message request codes */
+#define BRLVGER_SET_DISPLAY_ON 0
+#define BRLVGER_SET_DISPLAY_VOLTAGE 1
+#define BRLVGER_GET_SERIAL 3
+#define BRLVGER_GET_HWVERSION 4
+#define BRLVGER_GET_FWVERSION 5
+#define BRLVGER_GET_LENGTH 6
+#define BRLVGER_SEND_BRAILLE 7
+#define BRLVGER_BEEP 9
+#if 0 /* not used and not sure they're working */
+#define BRLVGER_GET_DISPLAY_VOLTAGE 2
+#define BRLVGER_GET_CURRENT 8
+#endif
+
+/* Prototypes */
+static void *brlvger_probe (struct usb_device *dev, unsigned ifnum,
+ const struct usb_device_id *id);
+static void brlvger_disconnect(struct usb_device *dev, void *ptr);
+static int brlvger_open(struct inode *inode, struct file *file);
+static int brlvger_release(struct inode *inode, struct file *file);
+static ssize_t brlvger_write(struct file *file, const char *buffer,
+ size_t count, loff_t *pos);
+static ssize_t brlvger_read(struct file *file, char *buffer,
+ size_t count, loff_t *unused_pos);
+static int brlvger_ioctl(struct inode *inode, struct file *file,
+ unsigned cmd, unsigned long arg);
+static unsigned brlvger_poll(struct file *file, poll_table *wait);
+static loff_t brlvger_llseek(struct file * file, loff_t offset, int orig);
+static void intr_callback(struct urb *urb);
+struct brlvger_priv;
+static int brlvger_get_hw_version(struct brlvger_priv *priv,
+ unsigned char *verbuf);
+static int brlvger_get_fw_version(struct brlvger_priv *priv,
+ unsigned char *buf);
+static int brlvger_get_serial(struct brlvger_priv *priv,
+ unsigned char *buf);
+static int brlvger_get_display_length(struct brlvger_priv *priv);
+static int brlvger_set_display_on_off(struct brlvger_priv *priv, __u16 on);
+static int brlvger_beep(struct brlvger_priv *priv, __u16 duration);
+static int brlvger_set_display_voltage(struct brlvger_priv *priv,
+ __u16 voltage);
+static int mycontrolmsg(const char *funcname,
+ struct brlvger_priv *priv, unsigned pipe_dir,
+ __u8 request, __u8 requesttype, __u16 value,
+ __u16 index, void *data, __u16 size);
+
+#define controlmsg(priv,pipe_dir,a,b,c,d,e,f) \
+ mycontrolmsg(__FUNCTION__, priv, pipe_dir, \
+ a,b,c,d,e,f)
+#define sndcontrolmsg(priv,a,b,c,d,e,f) \
+ controlmsg(priv, 0, a,b,c,d,e,f)
+#define rcvcontrolmsg(priv,a,b,c,d,e,f) \
+ controlmsg(priv, USB_DIR_IN, a,b,c,d,e,f)
+
+extern devfs_handle_t usb_devfs_handle; /* /dev/usb dir. */
+
+/* ----------------------------------------------------------------------- */
+
+/* Data */
+
+/* key event queue size */
+#define MAX_INTERRUPT_BUFFER 10
+
+/* private state */
+struct brlvger_priv {
+ struct usb_device *dev; /* USB device handle */
+ struct usb_endpoint_descriptor *in_interrupt;
+ struct urb *intr_urb;
+ devfs_handle_t devfs;
+
+ int subminor; /* which minor dev #? */
+
+ unsigned char hwver[BRLVGER_HWVER_SIZE]; /* hardware version */
+ unsigned char fwver[BRLVGER_FWVER_SIZE]; /* firmware version */
+ unsigned char serialnum[BRLVGER_SERIAL_SIZE];
+
+ int llength; /* logical length */
+ int plength; /* physical length */
+
+ __u8 obuf[MAX_BRLVGER_CELLS];
+ __u8 intr_buff[MAX_INTERRUPT_DATA];
+ __u8 event_queue[MAX_INTERRUPT_BUFFER][MAX_INTERRUPT_DATA];
+ atomic_t intr_idx, read_idx;
+ spinlock_t intr_idx_lock; /* protects intr_idx */
+ wait_queue_head_t read_wait;
+
+ int opened;
+ struct semaphore open_sem; /* protects ->opened */
+ struct semaphore dev_sem; /* protects ->dev */
+};
+
+/* Globals */
+
+/* Table of connected devices, a different minor for each. */
+static struct brlvger_priv *display_table[ MAX_NR_BRLVGER_DEVS ];
+
+/* Mutex for the operation of removing a device from display_table */
+static DECLARE_MUTEX(disconnect_sem);
+
+/* For blocking open */
+static DECLARE_WAIT_QUEUE_HEAD(open_wait);
+
+/* Some print macros */
+#ifdef dbg
+#undef dbg
+#endif
+#ifdef info
+#undef info
+#endif
+#ifdef err
+#undef err
+#endif
+#define info(args...) \
+ ({ printk(KERN_INFO "Voyager: " args); \
+ printk("\n"); })
+#define err(args...) \
+ ({ printk(KERN_ERR "Voyager: " args); \
+ printk("\n"); })
+#define dbgprint(args...) \
+ ({ printk(KERN_DEBUG "Voyager: " __FUNCTION__ ": " args); \
+ printk("\n"); })
+#define dbg(args...) \
+ ({ if(debug >= 1) dbgprint(args); })
+#define dbg2(args...) \
+ ({ if(debug >= 2) dbgprint(args); })
+#define dbg3(args...) \
+ ({ if(debug >= 3) dbgprint(args); })
+
+/* ----------------------------------------------------------------------- */
+
+/* Driver registration */
+
+static struct usb_device_id brlvger_ids [] = {
+ { USB_DEVICE(0x0798, 0x0001) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE (usb, brlvger_ids);
+
+static struct file_operations brlvger_fops =
+{
+ owner: THIS_MODULE,
+ llseek: brlvger_llseek,
+ read: brlvger_read,
+ write: brlvger_write,
+ ioctl: brlvger_ioctl,
+ open: brlvger_open,
+ release: brlvger_release,
+ poll: brlvger_poll,
+};
+
+static struct usb_driver brlvger_driver =
+{
+ name: "brlvger",
+ probe: brlvger_probe,
+ disconnect: brlvger_disconnect,
+ fops: &brlvger_fops,
+ minor: BRLVGER_MINOR,
+ id_table: brlvger_ids,
+};
+
+static int
+__init brlvger_init (void)
+{
+ printk(BANNER);
+
+ if(stall_tries < 1 || write_repeats < 1)
+ return -EINVAL;
+
+ memset(display_table, 0, sizeof(display_table));
+
+ if (usb_register(&brlvger_driver)) {
+ err("USB registration failed");
+ return -ENOSYS;
+ }
+
+ return 0;
+}
+
+static void
+__exit brlvger_cleanup (void)
+{
+ usb_deregister (&brlvger_driver);
+ dbg("Driver unregistered");
+}
+
+module_init (brlvger_init);
+module_exit (brlvger_cleanup);
+
+/* ----------------------------------------------------------------------- */
+
+/* Probe and disconnect functions */
+
+static void *
+brlvger_probe (struct usb_device *dev, unsigned ifnum,
+ const struct usb_device_id *id)
+{
+ struct brlvger_priv *priv = NULL;
+ int i;
+ struct usb_endpoint_descriptor *endpoint;
+ struct usb_interface_descriptor *actifsettings;
+ /* protects against reentrance: once we've found a free slot
+ we reserve it.*/
+ static DECLARE_MUTEX(reserve_sem);
+ char devfs_name[16];
+
+ actifsettings = dev->actconfig->interface->altsetting;
+
+ if( dev->descriptor.bNumConfigurations != 1
+ || dev->config->bNumInterfaces != 1
+ || actifsettings->bNumEndpoints != 1 ) {
+ err ("Bogus braille display config info");
+ return NULL;
+ }
+
+ endpoint = actifsettings->endpoint;
+ if (!(endpoint->bEndpointAddress & 0x80) ||
+ ((endpoint->bmAttributes & 3) != 0x03)) {
+ err ("Bogus braille display config info, wrong endpoints");
+ return NULL;
+ }
+
+ down(&reserve_sem);
+
+ for( i = 0; i < MAX_NR_BRLVGER_DEVS; i++ )
+ if( display_table[i] == NULL )
+ break;
+
+ if( i == MAX_NR_BRLVGER_DEVS ) {
+ err( "This driver cannot handle more than %d "
+ "braille displays", MAX_NR_BRLVGER_DEVS);
+ goto error;
+ }
+
+ if( !(priv = kmalloc (sizeof *priv, GFP_KERNEL)) ){
+ err("No more memory");
+ goto error;
+ }
+
+ memset(priv, 0, sizeof(*priv));
+ atomic_set(&priv->intr_idx, 0);
+ atomic_set(&priv->read_idx, MAX_INTERRUPT_BUFFER-1);
+ spin_lock_init(&priv->intr_idx_lock);
+ init_waitqueue_head(&priv->read_wait);
+ /* opened is memset'ed to 0 */
+ init_MUTEX(&priv->open_sem);
+ init_MUTEX(&priv->dev_sem);
+
+ priv->subminor = i;
+
+ /* we found a interrupt in endpoint */
+ priv->in_interrupt = endpoint;
+
+ priv->dev = dev;
+
+ if(brlvger_get_hw_version(priv, priv->hwver) <0) {
+ err("Unable to get hardware version");
+ goto error;
+ }
+ dbg("Hw ver %d.%d", priv->hwver[0], priv->hwver[1]);
+ if(brlvger_get_fw_version(priv, priv->fwver) <0) {
+ err("Unable to get firmware version");
+ goto error;
+ }
+ dbg("Fw ver: %s", priv->fwver);
+
+ if(brlvger_get_serial(priv, priv->serialnum) <0) {
+ err("Unable to get serial number");
+ goto error;
+ }
+ dbg("Serial number: %s", priv->serialnum);
+
+ if( (priv->llength = brlvger_get_display_length(priv)) <0 ){
+ err("Unable to get display length");
+ goto error;
+ }
+ switch(priv->llength) {
+ case 48:
+ priv->plength = 44;
+ break;
+ case 72:
+ priv->plength = 70;
+ break;
+ default:
+ err("Unsupported display length: %d", priv->llength);
+ goto error;
+ };
+ dbg("Display length: %d", priv->plength);
+
+ sprintf(devfs_name, "brlvger%d", priv->subminor);
+ priv->devfs = devfs_register(usb_devfs_handle, devfs_name,
+ DEVFS_FL_DEFAULT, USB_MAJOR,
+ BRLVGER_MINOR+priv->subminor,
+ S_IFCHR |S_IRUSR|S_IWUSR |S_IRGRP|S_IWGRP,
+ &brlvger_fops, NULL);
+ if (!priv->devfs) {
+#ifdef CONFIG_DEVFS_FS
+ err("devfs node registration failed");
+#endif
+ }
+
+ display_table[i] = priv;
+
+ info( "Braille display %d is device major %d minor %d",
+ i, USB_MAJOR, BRLVGER_MINOR + i);
+
+ /* Tell anyone waiting on a blocking open */
+ wake_up_interruptible(&open_wait);
+
+ goto out;
+
+ error:
+ if(priv) {
+ kfree( priv );
+ priv = NULL;
+ }
+
+ out:
+ up(&reserve_sem);
+ return priv;
+}
+
+static void
+brlvger_disconnect(struct usb_device *dev, void *ptr)
+{
+ struct brlvger_priv *priv = (struct brlvger_priv *)ptr;
+ int r;
+
+ if(priv){
+ info("Display %d disconnecting", priv->subminor);
+
+ devfs_unregister(priv->devfs);
+
+ down(&disconnect_sem);
+ display_table[priv->subminor] = NULL;
+ up(&disconnect_sem);
+
+ down(&priv->open_sem);
+ down(&priv->dev_sem);
+ if(priv->opened) {
+ /* Disable interrupts */
+ if((r = usb_unlink_urb(priv->intr_urb)) <0)
+ err("usb_unlink_urb returns %d", r);
+ usb_free_urb(priv->intr_urb);
+ /* mark device as dead and prevent control
+ messages to it */
+ priv->dev = NULL;
+ /* Tell anyone hung up on a read that it
+ won't be coming */
+ wake_up_interruptible(&priv->read_wait);
+ up(&priv->dev_sem);
+ up(&priv->open_sem);
+ }else
+ /* no corresponding up()s */
+ kfree(priv);
+ }
+}
+
+/* ----------------------------------------------------------------------- */
+
+/* fops implementation */
+
+static int
+brlvger_open(struct inode *inode, struct file *file)
+{
+ int devnum = MINOR (inode->i_rdev);
+ struct brlvger_priv *priv;
+ int n, ret;
+
+ if (devnum < BRLVGER_MINOR
+ || devnum >= (BRLVGER_MINOR + MAX_NR_BRLVGER_DEVS))
+ return -ENXIO;
+
+ n = devnum - BRLVGER_MINOR;
+
+ MOD_INC_USE_COUNT;
+
+ do {
+ down(&disconnect_sem);
+ priv = display_table[n];
+
+ if(!priv) {
+ up(&disconnect_sem);
+ if (file->f_flags & O_NONBLOCK) {
+ dbg3("Failing non-blocking open: "
+ "device %d not connected", n);
+ MOD_DEC_USE_COUNT;
+ return -EAGAIN;
+ }
+ /* Blocking open. One global wait queue will
+ suffice. We wait until a device for the selected
+ minor is connected. */
+ dbg2("Waiting for device %d to be connected", n);
+ ret = wait_event_interruptible(open_wait,
+ display_table[n]
+ != NULL);
+ if(ret) {
+ dbg2("Interrupted wait for device %d", n);
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+ }
+ } while(!priv);
+ /* We grabbed an existing device. */
+
+ if(down_interruptible(&priv->open_sem))
+ return -ERESTARTSYS;
+ up(&disconnect_sem);
+
+ /* Only one process can open each device, no sharing. */
+ ret = -EBUSY;
+ if(priv->opened)
+ goto error;
+
+ dbg("Opening display %d", priv->subminor);
+
+ /* Setup interrupt handler for receiving key input */
+ priv->intr_urb = usb_alloc_urb(0);
+ if(!priv->intr_urb) {
+ err("Unable to allocate URB");
+ goto error;
+ }
+ FILL_INT_URB( priv->intr_urb, priv->dev,
+ usb_rcvintpipe(priv->dev,
+ priv->in_interrupt->bEndpointAddress),
+ priv->intr_buff, sizeof(priv->intr_buff),
+ intr_callback, priv, priv->in_interrupt->bInterval);
+ if((ret = usb_submit_urb(priv->intr_urb)) <0){
+ err("Error %d while submitting URB", ret);
+ goto error;
+ }
+
+ /* Set voltage */
+ if(brlvger_set_display_voltage(priv, raw_voltage) <0) {
+ err("Unable to set voltage");
+ goto error;
+ }
+
+ /* Turn display on */
+ if((ret = brlvger_set_display_on_off(priv, 1)) <0) {
+ err("Error %d while turning display on", ret);
+ goto error;
+ }
+
+ /* Mark as opened, so disconnect cannot free priv. */
+ priv->opened = 1;
+
+ file->private_data = priv;
+
+ ret = 0;
+ goto out;
+
+ error:
+ MOD_DEC_USE_COUNT;
+ out:
+ up(&priv->open_sem);
+ return ret;
+}
+
+static int
+brlvger_release(struct inode *inode, struct file *file)
+{
+ struct brlvger_priv *priv = file->private_data;
+ int r;
+
+ /* Turn display off. Safe even if disconnected. */
+ brlvger_set_display_on_off(priv, 0);
+
+ /* mutex with disconnect and with open */
+ down(&priv->open_sem);
+
+ if(!priv->dev) {
+ dbg("Releasing disconnected device %d", priv->subminor);
+ /* no up(&priv->open_sem) */
+ kfree(priv);
+ }else{
+ dbg("Closing display %d", priv->subminor);
+ /* Disable interrupts */
+ if((r = usb_unlink_urb(priv->intr_urb)) <0)
+ err("usb_unlink_urb returns %d", r);
+ usb_free_urb(priv->intr_urb);
+ priv->opened = 0;
+ up(&priv->open_sem);
+ }
+
+ MOD_DEC_USE_COUNT;
+
+ return 0;
+}
+
+static ssize_t
+brlvger_write(struct file *file, const char *buffer,
+ size_t count, loff_t *pos)
+{
+ struct brlvger_priv *priv = file->private_data;
+ char buf[MAX_BRLVGER_CELLS];
+ int ret;
+ int rs, off;
+ __u16 written;
+
+ if(!priv->dev)
+ return -ENOLINK;
+
+ off = *pos;
+
+ if(off > priv->plength)
+ return -ESPIPE;;
+
+ rs = priv->plength - off;
+
+ if(count > rs)
+ count = rs;
+ written = count;
+
+ if (copy_from_user (buf, buffer, count ) )
+ return -EFAULT;
+
+ memset(priv->obuf, 0xaa, sizeof(priv->obuf));
+
+ /* Firmware supports multiples of 8cells, so some cells are absent
+ and for some reason there actually are holes! euurkkk! */
+
+ if( priv->plength == 44 ) {
+ /* Two ghost cells at the beginning of the display, plus
+ two more after the sixth physical cell. */
+ if(off > 5) {
+ off +=4;
+ memcpy(priv->obuf, buf, count);
+ }else{
+ int firstpart = 6 - off;
+
+#ifdef WRITE_DEBUG
+ dbg3("off: %d, rs: %d, count: %d, firstpart: %d",
+ off, rs, count, firstpart);
+#endif
+
+ firstpart = (firstpart < count) ? firstpart : count;
+
+#ifdef WRITE_DEBUG
+ dbg3("off: %d", off);
+ dbg3("firstpart: %d", firstpart);
+#endif
+
+ memcpy(priv->obuf, buf, firstpart);
+
+ if(firstpart != count) {
+ int secondpart = count - firstpart;
+#ifdef WRITE_DEBUG
+ dbg3("secondpart: %d", secondpart);
+#endif
+
+ memcpy(priv->obuf+(firstpart+2),
+ buf+firstpart, secondpart);
+ written +=2;
+ }
+
+ off +=2;
+
+#ifdef WRITE_DEBUG
+ dbg3("off: %d, rs: %d, count: %d, firstpart: %d, "
+ "written: %d", off, rs, count, firstpart, written);
+#endif
+ }
+ }else{
+ /* Two ghost cells at the beginningg of the display. */
+ memcpy(priv->obuf, buf, count);
+ off += 2;
+ }
+
+ {
+ int repeat = write_repeats;
+ /* Dirty hack: sometimes some of the dots are wrong and somehow
+ right themselves if the command is repeated. */
+ while(repeat--) {
+ ret = sndcontrolmsg(priv,
+ BRLVGER_SEND_BRAILLE, BRLVGER_WRITE_REQ, 0,
+ off, priv->obuf, written);
+ if(ret <0)
+ return ret;
+ }
+ }
+
+ return count;
+}
+
+static int
+read_index(struct brlvger_priv *priv)
+{
+ int intr_idx, read_idx;
+
+ read_idx = atomic_read(&priv->read_idx);
+ read_idx = ++read_idx == MAX_INTERRUPT_BUFFER ? 0 : read_idx;
+
+ intr_idx = atomic_read(&priv->intr_idx);
+
+ return(read_idx == intr_idx ? -1 : read_idx);
+}
+
+static ssize_t
+brlvger_read(struct file *file, char *buffer,
+ size_t count, loff_t *unused_pos)
+{
+ struct brlvger_priv *priv = file->private_data;
+ int read_idx;
+
+ if(count != MAX_INTERRUPT_DATA)
+ return -EINVAL;
+
+ if(!priv->dev)
+ return -ENOLINK;
+
+ if((read_idx = read_index(priv)) == -1) {
+ /* queue empty */
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ else{
+ int r = wait_event_interruptible(priv->read_wait,
+ (!priv->dev || (read_idx = read_index(priv)) != -1));
+ if(!priv->dev)
+ return -ENOLINK;
+ if(r)
+ return r;
+ if(read_idx == -1)
+ /* should not happen */
+ return 0;
+ }
+ }
+
+ if (copy_to_user (buffer, priv->event_queue[read_idx], count) )
+ return( -EFAULT);
+
+ atomic_set(&priv->read_idx, read_idx);
+ /* Multiple opens are not allowed. Yet on SMP, two processes could
+ read at the same time (on a shared file descriptor); then it is not
+ deterministic whether or not they will get duplicates of a key
+ event. */
+ return MAX_INTERRUPT_DATA;
+}
+
+static int
+brlvger_ioctl(struct inode *inode, struct file *file,
+ unsigned cmd, unsigned long arg)
+{
+ struct brlvger_priv *priv = file->private_data;
+
+ if(!priv->dev)
+ return -ENOLINK;
+
+ switch(cmd) {
+ case BRLVGER_GET_INFO: {
+ struct brlvger_info vi;
+
+ strncpy(vi.driver_version, DRIVER_VERSION,
+ sizeof(vi.driver_version));
+ vi.driver_version[sizeof(vi.driver_version)-1] = 0;
+ strncpy(vi.driver_banner, longbanner,
+ sizeof(vi.driver_banner));
+ vi.driver_banner[sizeof(vi.driver_banner)-1] = 0;
+
+ vi.display_length = priv->plength;
+
+ memcpy(&vi.hwver, priv->hwver, BRLVGER_HWVER_SIZE);
+ memcpy(&vi.fwver, priv->fwver, BRLVGER_FWVER_SIZE);
+ memcpy(&vi.serialnum, priv->serialnum, BRLVGER_SERIAL_SIZE);
+
+ if(copy_to_user((void *)arg, &vi, sizeof(vi)))
+ return -EFAULT;
+ return 0;
+ }
+ case BRLVGER_DISPLAY_ON:
+ return brlvger_set_display_on_off(priv, 1);
+ case BRLVGER_DISPLAY_OFF:
+ return brlvger_set_display_on_off(priv, 0);
+ case BRLVGER_BUZZ: {
+ __u16 duration;
+ if(get_user(duration, (__u16 *)arg))
+ return -EFAULT;
+ return brlvger_beep(priv, duration);
+ }
+
+#if 0 /* Underlying commands don't seem to work for some reason; not clear if
+ we'd want to export these anyway. */
+ case BRLVGER_SET_VOLTAGE: {
+ __u16 voltage;
+ if(get_user(voltage, (__u16 *)arg))
+ return -EFAULT;
+ return brlvger_set_display_voltage(priv, voltage);
+ }
+ case BRLVGER_GET_VOLTAGE: {
+ __u8 voltage;
+ int r = brlvger_get_display_voltage(priv);
+ if(r <0)
+ return r;
+ voltage = r;
+ if(put_user(voltage, (__u8 *)arg))
+ return -EFAULT;
+ return 0;
+ }
+#endif
+ default:
+ return -EINVAL;
+ };
+}
+
+static loff_t
+brlvger_llseek(struct file *file, loff_t offset, int orig)
+{
+ struct brlvger_priv *priv = file->private_data;
+
+ if(!priv->dev)
+ return -ENOLINK;
+
+ switch (orig) {
+ case 0:
+ /* nothing to do */
+ break;
+ case 1:
+ offset +=file->f_pos;
+ break;
+ case 2:
+ offset += priv->plength;
+ default:
+ return -EINVAL;
+ }
+
+ if((offset >= priv->plength) || (offset < 0))
+ return -EINVAL;
+
+ return (file->f_pos = offset);
+}
+
+static unsigned
+brlvger_poll(struct file *file, poll_table *wait)
+{
+ struct brlvger_priv *priv = file->private_data;
+
+ if(!priv->dev)
+ return POLLERR | POLLHUP;
+
+ poll_wait(file, &priv->read_wait, wait);
+
+ if(!priv->dev)
+ return POLLERR | POLLHUP;
+ if(read_index(priv) != -1)
+ return POLLIN | POLLRDNORM;
+
+ return 0;
+}
+
+static void
+intr_callback(struct urb *urb)
+{
+ struct brlvger_priv *priv = urb->context;
+ int intr_idx, read_idx;
+
+ if( urb->status ) {
+ if(urb->status == -ETIMEDOUT)
+ dbg2("Status -ETIMEDOUT, "
+ "probably disconnected");
+ else if(urb->status != -ENOENT)
+ err("Status: %d", urb->status);
+ return;
+ }
+
+ read_idx = atomic_read(&priv->read_idx);
+ spin_lock(&priv->intr_idx_lock);
+ intr_idx = atomic_read(&priv->intr_idx);
+ if(read_idx == intr_idx) {
+ dbg2("Queue full, dropping braille display input");
+ spin_unlock(&priv->intr_idx_lock);
+ return; /* queue full */
+ }
+
+ memcpy(priv->event_queue[intr_idx], urb->transfer_buffer,
+ MAX_INTERRUPT_DATA);
+
+ intr_idx = (++intr_idx == MAX_INTERRUPT_BUFFER)? 0 : intr_idx;
+ atomic_set(&priv->intr_idx, intr_idx);
+ spin_unlock(&priv->intr_idx_lock);
+
+ wake_up_interruptible(&priv->read_wait);
+}
+
+/* ----------------------------------------------------------------------- */
+
+/* Hardware access functions */
+
+static int
+mycontrolmsg(const char *funcname,
+ struct brlvger_priv *priv, unsigned pipe_dir,
+ __u8 request, __u8 requesttype, __u16 value,
+ __u16 index, void *data, __u16 size)
+{
+ int ret=0, tries = stall_tries;
+
+ /* Make sure the device was not disconnected */
+ if(down_interruptible(&priv->dev_sem))
+ return -ERESTARTSYS;
+ if(!priv->dev) {
+ up(&priv->dev_sem);
+ return -ENOLINK;
+ }
+
+ /* Dirty hack for retransmission: stalls and fails all the time
+ without this on the hardware we tested. */
+ while(tries--) {
+ ret = usb_control_msg(priv->dev,
+ usb_sndctrlpipe(priv->dev,0) |pipe_dir,
+ request, requesttype, value,
+ index, data, size,
+ HZ);
+ if(ret != -EPIPE)
+ break;
+ dbg2("Stalled, remaining %d tries", tries);
+ }
+ up(&priv->dev_sem);
+ if(ret <0) {
+ err("%s: usb_control_msg returns %d",
+ funcname, ret);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int
+brlvger_get_hw_version(struct brlvger_priv *priv, unsigned char *verbuf)
+{
+ return rcvcontrolmsg(priv,
+ BRLVGER_GET_HWVERSION, BRLVGER_READ_REQ, 0,
+ 0, verbuf, BRLVGER_HWVER_SIZE);
+ /* verbuf should be 2 bytes */
+}
+
+static int
+brlvger_get_fw_version(struct brlvger_priv *priv, unsigned char *buf)
+{
+ unsigned char rawbuf[(BRLVGER_FWVER_SIZE-1)*2+2];
+ int i, len;
+ int r = rcvcontrolmsg(priv,
+ BRLVGER_GET_FWVERSION, BRLVGER_READ_REQ, 0,
+ 0, rawbuf, sizeof(rawbuf));
+ if(r<0)
+ return r;
+
+ /* If I guess correctly: succession of 16bit words, the string is
+ formed of the first byte of each of these words. First byte in
+ buffer indicates total length of data; not sure what second byte is
+ for. */
+ len = rawbuf[0]-2;
+ if(len<0)
+ len = 0;
+ else if(len+1 > BRLVGER_FWVER_SIZE)
+ len = BRLVGER_FWVER_SIZE-1;
+ for(i=0; i<len; i++)
+ buf[i] = rawbuf[2+2*i];
+ buf[i] = 0;
+ return 0;
+}
+
+static int
+brlvger_get_serial(struct brlvger_priv *priv, unsigned char *buf)
+{
+ unsigned char rawserial[BRLVGER_SERIAL_BIN_SIZE];
+ int i;
+ int r = rcvcontrolmsg(priv,
+ BRLVGER_GET_SERIAL, BRLVGER_READ_REQ, 0,
+ 0, rawserial, sizeof(rawserial));
+ if(r<0)
+ return r;
+
+ for(i=0; i<BRLVGER_SERIAL_BIN_SIZE; i++) {
+#define NUM_TO_HEX(n) (((n)>9) ? (n)+'A' : (n)+'0')
+ buf[2*i] = NUM_TO_HEX(rawserial[i] >>4);
+ buf[2*i+1] = NUM_TO_HEX(rawserial[i] &0xf);
+ }
+ buf[2*i] = 0;
+ return 0;
+}
+
+static int
+brlvger_get_display_length(struct brlvger_priv *priv)
+{
+ unsigned char data[2];
+ int ret = rcvcontrolmsg(priv,
+ BRLVGER_GET_LENGTH, BRLVGER_READ_REQ, 0,
+ 0, data, 2);
+ if(ret<0)
+ return ret;
+ return data[1];
+}
+
+static int
+brlvger_beep(struct brlvger_priv *priv, __u16 duration)
+{
+ return sndcontrolmsg(priv,
+ BRLVGER_BEEP, BRLVGER_WRITE_REQ, duration,
+ 0, NULL, 0);
+}
+
+static int
+brlvger_set_display_on_off(struct brlvger_priv *priv, __u16 on)
+{
+ dbg2("Turning display %s", ((on) ? "on" : "off"));
+ return sndcontrolmsg(priv,
+ BRLVGER_SET_DISPLAY_ON, BRLVGER_WRITE_REQ, on,
+ 0, NULL, 0);
+}
+
+static int
+brlvger_set_display_voltage(struct brlvger_priv *priv, __u16 voltage)
+{
+ dbg("SET_DISPLAY_VOLTAGE to %u", voltage);
+ return sndcontrolmsg(priv,
+ BRLVGER_SET_DISPLAY_VOLTAGE, BRLVGER_WRITE_REQ, voltage,
+ 0, NULL, 0);
+}
+
+#if 0 /* Had problems testing these commands. Not particularly useful anyway.*/
+
+static int
+brlvger_get_display_voltage(struct brlvger_priv *priv)
+{
+ __u8 voltage = 0;
+ int ret = rcvcontrolmsg(priv,
+ BRLVGER_GET_DISPLAY_VOLTAGE, BRLVGER_READ_REQ, 0,
+ 0, &voltage, 1);
+ if(ret<0)
+ return ret;
+ return voltage;
+}
+
+static int
+brlvger_get_current(struct brlvger_priv *priv)
+{
+ unsigned char data;
+ int ret = rcvcontrolmsg(priv,
+ BRLVGER_GET_CURRENT, BRLVGER_READ_REQ, 0,
+ 0, &data, 1);
+ if(ret<0)
+ return ret;
+ return data;
+}
+#endif
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)