| /* |
| * CPC-USB CAN Interface Kernel Driver |
| * |
| * Copyright (C) 2004-2009 EMS Dr. Thomas Wuensche |
| * |
| * 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; version 2 of the License. |
| * |
| * 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., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/vmalloc.h> |
| #include <linux/module.h> |
| #include <linux/poll.h> |
| #include <linux/smp_lock.h> |
| #include <linux/completion.h> |
| #include <asm/uaccess.h> |
| #include <linux/usb.h> |
| |
| |
| #include <linux/proc_fs.h> |
| |
| #include "cpc.h" |
| |
| #include "cpc_int.h" |
| #include "cpcusb.h" |
| |
| #include "sja2m16c.h" |
| |
| /* Version Information */ |
| #define DRIVER_AUTHOR "Sebastian Haas <haas@ems-wuensche.com>" |
| #define DRIVER_DESC "CPC-USB Driver for Linux Kernel 2.6" |
| #define DRIVER_VERSION CPC_DRIVER_VERSION |
| |
| MODULE_AUTHOR(DRIVER_AUTHOR); |
| MODULE_DESCRIPTION(DRIVER_DESC); |
| MODULE_VERSION(DRIVER_VERSION); |
| MODULE_LICENSE("GPL v2"); |
| |
| /* Define these values to match your devices */ |
| #define USB_CPCUSB_VENDOR_ID 0x12D6 |
| |
| #define USB_CPCUSB_M16C_PRODUCT_ID 0x0888 |
| #define USB_CPCUSB_LPC2119_PRODUCT_ID 0x0444 |
| |
| #define CPC_USB_PROC_DIR CPC_PROC_DIR "cpc-usb" |
| |
| static struct proc_dir_entry *procDir; |
| static struct proc_dir_entry *procEntry; |
| |
| /* Module parameters */ |
| static int debug; |
| module_param(debug, int, S_IRUGO); |
| |
| /* table of devices that work with this driver */ |
| static struct usb_device_id cpcusb_table[] = { |
| {USB_DEVICE(USB_CPCUSB_VENDOR_ID, USB_CPCUSB_M16C_PRODUCT_ID)}, |
| {USB_DEVICE(USB_CPCUSB_VENDOR_ID, USB_CPCUSB_LPC2119_PRODUCT_ID)}, |
| {} /* Terminating entry */ |
| }; |
| |
| MODULE_DEVICE_TABLE(usb, cpcusb_table); |
| |
| /* use to prevent kernel panic if driver is unloaded |
| * while a programm has still open the device |
| */ |
| DECLARE_WAIT_QUEUE_HEAD(rmmodWq); |
| atomic_t useCount; |
| |
| static CPC_USB_T *CPCUSB_Table[CPC_USB_CARD_CNT] = { 0 }; |
| static unsigned int CPCUsbCnt; |
| |
| /* prevent races between open() and disconnect() */ |
| static DECLARE_MUTEX(disconnect_sem); |
| |
| /* local function prototypes */ |
| static ssize_t cpcusb_read(struct file *file, char *buffer, size_t count, |
| loff_t *ppos); |
| static ssize_t cpcusb_write(struct file *file, const char *buffer, |
| size_t count, loff_t *ppos); |
| static unsigned int cpcusb_poll(struct file *file, poll_table * wait); |
| static int cpcusb_open(struct inode *inode, struct file *file); |
| static int cpcusb_release(struct inode *inode, struct file *file); |
| |
| static int cpcusb_probe(struct usb_interface *interface, |
| const struct usb_device_id *id); |
| static void cpcusb_disconnect(struct usb_interface *interface); |
| |
| static void cpcusb_read_bulk_callback(struct urb *urb); |
| static void cpcusb_write_bulk_callback(struct urb *urb); |
| static void cpcusb_read_interrupt_callback(struct urb *urb); |
| |
| static int cpcusb_setup_intrep(CPC_USB_T *card); |
| |
| static struct file_operations cpcusb_fops = { |
| /* |
| * The owner field is part of the module-locking |
| * mechanism. The idea is that the kernel knows |
| * which module to increment the use-counter of |
| * BEFORE it calls the device's open() function. |
| * This also means that the kernel can decrement |
| * the use-counter again before calling release() |
| * or should the open() function fail. |
| */ |
| .owner = THIS_MODULE, |
| |
| .read = cpcusb_read, |
| .write = cpcusb_write, |
| .poll = cpcusb_poll, |
| .open = cpcusb_open, |
| .release = cpcusb_release, |
| }; |
| |
| /* |
| * usb class driver info in order to get a minor number from the usb core, |
| * and to have the device registered with devfs and the driver core |
| */ |
| static struct usb_class_driver cpcusb_class = { |
| .name = "usb/cpc_usb%d", |
| .fops = &cpcusb_fops, |
| .minor_base = CPC_USB_BASE_MNR, |
| }; |
| |
| /* usb specific object needed to register this driver with the usb subsystem */ |
| static struct usb_driver cpcusb_driver = { |
| .name = "cpc-usb", |
| .probe = cpcusb_probe, |
| .disconnect = cpcusb_disconnect, |
| .id_table = cpcusb_table, |
| }; |
| |
| static int cpcusb_create_info_output(char *buf) |
| { |
| int i = 0, j; |
| |
| for (j = 0; j < CPC_USB_CARD_CNT; j++) { |
| if (CPCUSB_Table[j]) { |
| CPC_USB_T *card = CPCUSB_Table[j]; |
| CPC_CHAN_T *chan = card->chan; |
| |
| /* MINOR CHANNELNO BUSNO SLOTNO */ |
| i += sprintf(&buf[i], "%d %s\n", chan->minor, |
| card->serialNumber); |
| } |
| } |
| |
| return i; |
| } |
| |
| static int cpcusb_proc_read_info(char *page, char **start, off_t off, |
| int count, int *eof, void *data) |
| { |
| int len = cpcusb_create_info_output(page); |
| |
| if (len <= off + count) |
| *eof = 1; |
| *start = page + off; |
| len -= off; |
| if (len > count) |
| len = count; |
| if (len < 0) |
| len = 0; |
| |
| return len; |
| } |
| |
| /* |
| * Remove CPC-USB and cleanup |
| */ |
| static inline void cpcusb_delete(CPC_USB_T *card) |
| { |
| if (card) { |
| if (card->chan) { |
| if (card->chan->buf) |
| vfree(card->chan->buf); |
| |
| if (card->chan->CPCWait_q) |
| kfree(card->chan->CPCWait_q); |
| |
| kfree(card->chan); |
| } |
| |
| CPCUSB_Table[card->idx] = NULL; |
| kfree(card); |
| } |
| } |
| |
| /* |
| * setup the interrupt IN endpoint of a specific CPC-USB device |
| */ |
| static int cpcusb_setup_intrep(CPC_USB_T *card) |
| { |
| int retval = 0; |
| struct usb_endpoint_descriptor *ep; |
| |
| ep = &card->interface->altsetting[0].endpoint[card->num_intr_in].desc; |
| |
| card->intr_in_buffer[0] = 0; |
| card->free_slots = 15; /* initial size */ |
| |
| /* setup the urb */ |
| usb_fill_int_urb(card->intr_in_urb, card->udev, |
| usb_rcvintpipe(card->udev, card->num_intr_in), |
| card->intr_in_buffer, |
| sizeof(card->intr_in_buffer), |
| cpcusb_read_interrupt_callback, |
| card, |
| ep->bInterval); |
| |
| card->intr_in_urb->status = 0; /* needed! */ |
| |
| /* submit the urb */ |
| retval = usb_submit_urb(card->intr_in_urb, GFP_KERNEL); |
| |
| if (retval) |
| err("%s - failed submitting intr urb, error %d", __func__, |
| retval); |
| |
| return retval; |
| } |
| |
| static int cpcusb_open(struct inode *inode, struct file *file) |
| { |
| CPC_USB_T *card = NULL; |
| struct usb_interface *interface; |
| int subminor; |
| int j, retval = 0; |
| |
| subminor = iminor(inode); |
| |
| /* prevent disconnects */ |
| down(&disconnect_sem); |
| |
| interface = usb_find_interface(&cpcusb_driver, subminor); |
| if (!interface) { |
| err("%s - error, can't find device for minor %d", |
| __func__, subminor); |
| retval = CPC_ERR_NO_INTERFACE_PRESENT; |
| goto exit_no_device; |
| } |
| |
| card = usb_get_intfdata(interface); |
| if (!card) { |
| retval = CPC_ERR_NO_INTERFACE_PRESENT; |
| goto exit_no_device; |
| } |
| |
| /* lock this device */ |
| down(&card->sem); |
| |
| /* increment our usage count for the driver */ |
| if (card->open) { |
| dbg("device already opened"); |
| retval = CPC_ERR_CHANNEL_ALREADY_OPEN; |
| goto exit_on_error; |
| } |
| |
| /* save our object in the file's private structure */ |
| file->private_data = card; |
| for (j = 0; j < CPC_USB_URB_CNT; j++) { |
| usb_fill_bulk_urb(card->urbs[j].urb, card->udev, |
| usb_rcvbulkpipe(card->udev, card->num_bulk_in), |
| card->urbs[j].buffer, card->urbs[j].size, |
| cpcusb_read_bulk_callback, card); |
| |
| retval = usb_submit_urb(card->urbs[j].urb, GFP_KERNEL); |
| |
| if (retval) { |
| err("%s - failed submitting read urb, error %d", |
| __func__, retval); |
| retval = CPC_ERR_TRANSMISSION_FAILED; |
| goto exit_on_error; |
| } |
| } |
| |
| info("%s - %d URB's submitted", __func__, j); |
| |
| ResetBuffer(card->chan); |
| |
| cpcusb_setup_intrep(card); |
| card->open = 1; |
| |
| atomic_inc(&useCount); |
| |
| exit_on_error: |
| /* unlock this device */ |
| up(&card->sem); |
| |
| exit_no_device: |
| up(&disconnect_sem); |
| |
| return retval; |
| } |
| |
| static unsigned int cpcusb_poll(struct file *file, poll_table * wait) |
| { |
| CPC_USB_T *card = (CPC_USB_T *) file->private_data; |
| unsigned int retval = 0; |
| |
| if (!card) { |
| err("%s - device object lost", __func__); |
| return -EIO; |
| } |
| |
| poll_wait(file, card->chan->CPCWait_q, wait); |
| |
| if (IsBufferNotEmpty(card->chan) || !(card->present)) |
| retval |= (POLLIN | POLLRDNORM); |
| |
| if (card->free_slots) |
| retval |= (POLLOUT | POLLWRNORM); |
| |
| return retval; |
| } |
| |
| static int cpcusb_release(struct inode *inode, struct file *file) |
| { |
| CPC_USB_T *card = (CPC_USB_T *) file->private_data; |
| int j, retval = 0; |
| |
| if (card == NULL) { |
| dbg("%s - object is NULL", __func__); |
| return CPC_ERR_NO_INTERFACE_PRESENT; |
| } |
| |
| /* lock our device */ |
| down(&card->sem); |
| |
| if (!card->open) { |
| dbg("%s - device not opened", __func__); |
| retval = CPC_ERR_NO_INTERFACE_PRESENT; |
| goto exit_not_opened; |
| } |
| |
| /* if device wasn't unplugged kill all urbs */ |
| if (card->present) { |
| /* kill read urbs */ |
| for (j = 0; j < CPC_USB_URB_CNT; j++) { |
| usb_kill_urb(card->urbs[j].urb); |
| } |
| |
| /* kill irq urb */ |
| usb_kill_urb(card->intr_in_urb); |
| |
| /* kill write urbs */ |
| for (j = 0; j < CPC_USB_URB_CNT; j++) { |
| if (atomic_read(&card->wrUrbs[j].busy)) { |
| usb_kill_urb(card->wrUrbs[j].urb); |
| wait_for_completion(&card->wrUrbs[j].finished); |
| } |
| } |
| } |
| |
| atomic_dec(&useCount); |
| |
| /* last process detached */ |
| if (atomic_read(&useCount) == 0) { |
| wake_up(&rmmodWq); |
| } |
| |
| if (!card->present && card->open) { |
| /* the device was unplugged before the file was released */ |
| up(&card->sem); |
| cpcusb_delete(card); |
| return 0; |
| } |
| |
| card->open = 0; |
| |
| exit_not_opened: |
| up(&card->sem); |
| |
| return 0; |
| } |
| |
| static ssize_t cpcusb_read(struct file *file, char *buffer, size_t count, |
| loff_t *ppos) |
| { |
| CPC_USB_T *card = (CPC_USB_T *) file->private_data; |
| CPC_CHAN_T *chan; |
| int retval = 0; |
| |
| if (count < sizeof(CPC_MSG_T)) |
| return CPC_ERR_UNKNOWN; |
| |
| /* check if can read from the given address */ |
| if (!access_ok(VERIFY_WRITE, buffer, count)) |
| return CPC_ERR_UNKNOWN; |
| |
| /* lock this object */ |
| down(&card->sem); |
| |
| /* verify that the device wasn't unplugged */ |
| if (!card->present) { |
| up(&card->sem); |
| return CPC_ERR_NO_INTERFACE_PRESENT; |
| } |
| |
| if (IsBufferEmpty(card->chan)) { |
| retval = 0; |
| } else { |
| chan = card->chan; |
| |
| #if 0 |
| /* convert LPC2119 params back to SJA1000 params */ |
| if (card->deviceRevision >= 0x0200 |
| && chan->buf[chan->oidx].type == CPC_MSG_T_CAN_PRMS) { |
| LPC2119_TO_SJA1000_Params(&chan->buf[chan->oidx]); |
| } |
| #endif |
| |
| if (copy_to_user(buffer, &chan->buf[chan->oidx], count) != 0) { |
| retval = CPC_ERR_IO_TRANSFER; |
| } else { |
| chan->oidx = (chan->oidx + 1) % CPC_MSG_BUF_CNT; |
| chan->WnR = 1; |
| retval = sizeof(CPC_MSG_T); |
| } |
| } |
| /* spin_unlock_irqrestore(&card->slock, flags); */ |
| |
| /* unlock the device */ |
| up(&card->sem); |
| |
| return retval; |
| } |
| |
| #define SHIFT 1 |
| static inline void cpcusb_align_buffer_alignment(unsigned char *buf) |
| { |
| /* CPC-USB uploads packed bytes. */ |
| CPC_MSG_T *cpc = (CPC_MSG_T *) buf; |
| unsigned int i; |
| |
| for (i = 0; i < cpc->length + (2 * sizeof(unsigned long)); i++) { |
| ((unsigned char *) &cpc->msgid)[1 + i] = |
| ((unsigned char *) &cpc->msgid)[1 + SHIFT + i]; |
| } |
| } |
| |
| static int cpc_get_buffer_count(CPC_CHAN_T *chan) |
| { |
| /* check the buffer parameters */ |
| if (chan->iidx == chan->oidx) |
| return !chan->WnR ? CPC_MSG_BUF_CNT : 0; |
| else if (chan->iidx >= chan->oidx) |
| return (chan->iidx - chan->oidx) % CPC_MSG_BUF_CNT; |
| |
| return (chan->iidx + CPC_MSG_BUF_CNT - chan->oidx) % CPC_MSG_BUF_CNT; |
| } |
| |
| static ssize_t cpcusb_write(struct file *file, const char *buffer, |
| size_t count, loff_t *ppos) |
| { |
| CPC_USB_T *card = (CPC_USB_T *) file->private_data; |
| CPC_USB_WRITE_URB_T *wrUrb = NULL; |
| |
| ssize_t bytes_written = 0; |
| int retval = 0; |
| int j; |
| |
| unsigned char *obuf = NULL; |
| unsigned char type = 0; |
| CPC_MSG_T *info = NULL; |
| |
| dbg("%s - entered minor %d, count = %zu, present = %d", |
| __func__, card->minor, count, card->present); |
| |
| if (count > sizeof(CPC_MSG_T)) |
| return CPC_ERR_UNKNOWN; |
| |
| /* check if can read from the given address */ |
| if (!access_ok(VERIFY_READ, buffer, count)) |
| return CPC_ERR_UNKNOWN; |
| |
| /* lock this object */ |
| down(&card->sem); |
| |
| /* verify that the device wasn't unplugged */ |
| if (!card->present) { |
| retval = CPC_ERR_NO_INTERFACE_PRESENT; |
| goto exit; |
| } |
| |
| /* verify that we actually have some data to write */ |
| if (count == 0) { |
| dbg("%s - write request of 0 bytes", __func__); |
| goto exit; |
| } |
| |
| if (card->free_slots <= 5) { |
| info = (CPC_MSG_T *) buffer; |
| |
| if (info->type != CPC_CMD_T_CLEAR_CMD_QUEUE |
| || card->free_slots <= 0) { |
| dbg("%s - send buffer full please try again %d", |
| __func__, card->free_slots); |
| retval = CPC_ERR_CAN_NO_TRANSMIT_BUF; |
| goto exit; |
| } |
| } |
| |
| /* Find a free write urb */ |
| for (j = 0; j < CPC_USB_URB_CNT; j++) { |
| if (!atomic_read(&card->wrUrbs[j].busy)) { |
| wrUrb = &card->wrUrbs[j]; /* remember found URB */ |
| atomic_set(&wrUrb->busy, 1); /* lock this URB */ |
| init_completion(&wrUrb->finished); /* init completion */ |
| dbg("WR URB no. %d started", j); |
| break; |
| } |
| } |
| |
| /* don't found write urb say error */ |
| if (!wrUrb) { |
| dbg("%s - no free send urb available", __func__); |
| retval = CPC_ERR_CAN_NO_TRANSMIT_BUF; |
| goto exit; |
| } |
| dbg("URB write req"); |
| |
| obuf = (unsigned char *) wrUrb->urb->transfer_buffer; |
| |
| /* copy the data from userspace into our transfer buffer; |
| * this is the only copy required. |
| */ |
| if (copy_from_user(&obuf[4], buffer, count) != 0) { |
| atomic_set(&wrUrb->busy, 0); /* release urb */ |
| retval = CPC_ERR_IO_TRANSFER; |
| goto exit; |
| } |
| |
| /* check if it is a DRIVER information message, so we can |
| * response to that message and not the USB |
| */ |
| info = (CPC_MSG_T *) &obuf[4]; |
| |
| bytes_written = 11 + info->length; |
| if (bytes_written >= wrUrb->size) { |
| retval = CPC_ERR_IO_TRANSFER; |
| goto exit; |
| } |
| |
| switch (info->type) { |
| case CPC_CMD_T_CLEAR_MSG_QUEUE: |
| ResetBuffer(card->chan); |
| break; |
| |
| case CPC_CMD_T_INQ_MSG_QUEUE_CNT: |
| retval = cpc_get_buffer_count(card->chan); |
| atomic_set(&wrUrb->busy, 0); |
| |
| goto exit; |
| |
| case CPC_CMD_T_INQ_INFO: |
| if (info->msg.info.source == CPC_INFOMSG_T_DRIVER) { |
| /* release urb cause we'll use it for driver |
| * information |
| */ |
| atomic_set(&wrUrb->busy, 0); |
| if (IsBufferFull(card->chan)) { |
| retval = CPC_ERR_IO_TRANSFER; |
| goto exit; |
| } |
| |
| /* it is a driver information request message and we have |
| * free rx slots to store the response |
| */ |
| type = info->msg.info.type; |
| info = &card->chan->buf[card->chan->iidx]; |
| |
| info->type = CPC_MSG_T_INFO; |
| info->msg.info.source = CPC_INFOMSG_T_DRIVER; |
| info->msg.info.type = type; |
| |
| switch (type) { |
| case CPC_INFOMSG_T_VERSION: |
| info->length = strlen(CPC_DRIVER_VERSION) + 2; |
| sprintf(info->msg.info.msg, "%s\n", |
| CPC_DRIVER_VERSION); |
| break; |
| |
| case CPC_INFOMSG_T_SERIAL: |
| info->length = strlen(CPC_DRIVER_SERIAL) + 2; |
| sprintf(info->msg.info.msg, "%s\n", |
| CPC_DRIVER_SERIAL); |
| break; |
| |
| default: |
| info->length = 2; |
| info->msg.info.type = |
| CPC_INFOMSG_T_UNKNOWN_TYPE; |
| } |
| |
| card->chan->WnR = 0; |
| card->chan->iidx = |
| (card->chan->iidx + 1) % CPC_MSG_BUF_CNT; |
| |
| retval = info->length; |
| goto exit; |
| } |
| break; |
| case CPC_CMD_T_CAN_PRMS: |
| /* Check the controller type. If it's the new CPC-USB, make sure if these are SJA1000 params */ |
| if (info->msg.canparams.cc_type != SJA1000 |
| && info->msg.canparams.cc_type != M16C_BASIC |
| && (card->productId == USB_CPCUSB_LPC2119_PRODUCT_ID |
| && info->msg.canparams.cc_type != SJA1000)) { |
| /* don't forget to release the urb */ |
| atomic_set(&wrUrb->busy, 0); |
| retval = CPC_ERR_WRONG_CONTROLLER_TYPE; |
| goto exit; |
| } |
| break; |
| } |
| |
| /* just convert the params if it is an old CPC-USB with M16C controller */ |
| if (card->productId == USB_CPCUSB_M16C_PRODUCT_ID) { |
| /* if it is a parameter message convert it from SJA1000 controller |
| * settings to M16C Basic controller settings |
| */ |
| SJA1000_TO_M16C_BASIC_Params((CPC_MSG_T *) &obuf[4]); |
| } |
| |
| /* don't forget the byte alignment */ |
| cpcusb_align_buffer_alignment(&obuf[4]); |
| |
| /* setup a the 4 byte header */ |
| obuf[0] = obuf[1] = obuf[2] = obuf[3] = 0; |
| |
| /* this urb was already set up, except for this write size */ |
| wrUrb->urb->transfer_buffer_length = bytes_written + 4; |
| |
| /* send the data out the bulk port */ |
| /* a character device write uses GFP_KERNEL, |
| unless a spinlock is held */ |
| retval = usb_submit_urb(wrUrb->urb, GFP_KERNEL); |
| if (retval) { |
| atomic_set(&wrUrb->busy, 0); /* release urb */ |
| err("%s - failed submitting write urb, error %d", |
| __func__, retval); |
| } else { |
| retval = bytes_written; |
| } |
| |
| exit: |
| /* unlock the device */ |
| up(&card->sem); |
| |
| dbg("%s - leaved", __func__); |
| |
| return retval; |
| } |
| |
| /* |
| * callback for interrupt IN urb |
| */ |
| static void cpcusb_read_interrupt_callback(struct urb *urb) |
| { |
| CPC_USB_T *card = (CPC_USB_T *) urb->context; |
| int retval; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&card->slock, flags); |
| |
| if (!card->present) { |
| spin_unlock_irqrestore(&card->slock, flags); |
| info("%s - no such device", __func__); |
| return; |
| } |
| |
| switch (urb->status) { |
| case 0: /* success */ |
| card->free_slots = card->intr_in_buffer[1]; |
| break; |
| case -ECONNRESET: |
| case -ENOENT: |
| case -ESHUTDOWN: |
| /* urb was killed */ |
| spin_unlock_irqrestore(&card->slock, flags); |
| dbg("%s - intr urb killed", __func__); |
| return; |
| default: |
| info("%s - nonzero urb status %d", __func__, urb->status); |
| break; |
| } |
| |
| retval = usb_submit_urb(urb, GFP_ATOMIC); |
| if (retval) { |
| err("%s - failed resubmitting intr urb, error %d", |
| __func__, retval); |
| } |
| |
| spin_unlock_irqrestore(&card->slock, flags); |
| wake_up_interruptible(card->chan->CPCWait_q); |
| |
| return; |
| } |
| |
| #define UN_SHIFT 1 |
| #define CPCMSG_HEADER_LEN_FIRMWARE 11 |
| static inline int cpcusb_unalign_and_copy_buffy(unsigned char *out, |
| unsigned char *in) |
| { |
| unsigned int i, j; |
| |
| for (i = 0; i < 3; i++) |
| out[i] = in[i]; |
| |
| for (j = 0; j < (in[1] + (CPCMSG_HEADER_LEN_FIRMWARE - 3)); j++) |
| out[j + i + UN_SHIFT] = in[j + i]; |
| |
| return i + j; |
| } |
| |
| /* |
| * callback for bulk IN urb |
| */ |
| static void cpcusb_read_bulk_callback(struct urb *urb) |
| { |
| CPC_USB_T *card = (CPC_USB_T *) urb->context; |
| CPC_CHAN_T *chan; |
| unsigned char *ibuf = urb->transfer_buffer; |
| int retval, msgCnt, start, again = 0; |
| unsigned long flags; |
| |
| if (!card) { |
| err("%s - device object lost", __func__); |
| return; |
| } |
| |
| spin_lock_irqsave(&card->slock, flags); |
| |
| if (!card->present) { |
| spin_unlock_irqrestore(&card->slock, flags); |
| info("%s - no such device", __func__); |
| return; |
| } |
| |
| switch (urb->status) { |
| case 0: /* success */ |
| break; |
| case -ECONNRESET: |
| case -ENOENT: |
| case -ESHUTDOWN: |
| /* urb was killed */ |
| spin_unlock_irqrestore(&card->slock, flags); |
| dbg("%s - read urb killed", __func__); |
| return; |
| default: |
| info("%s - nonzero urb status %d", __func__, urb->status); |
| break; |
| } |
| |
| if (urb->actual_length) { |
| msgCnt = ibuf[0] & ~0x80; |
| again = ibuf[0] & 0x80; |
| |
| /* we have a 4 byte header */ |
| start = 4; |
| chan = card->chan; |
| while (msgCnt) { |
| if (!(IsBufferFull(card->chan))) { |
| start += |
| cpcusb_unalign_and_copy_buffy((unsigned char *) |
| &chan->buf[chan->iidx], &ibuf[start]); |
| |
| if (start > urb->transfer_buffer_length) { |
| err("%d > %d", start, urb->transfer_buffer_length); |
| break; |
| } |
| |
| chan->WnR = 0; |
| chan->iidx = (chan->iidx + 1) % CPC_MSG_BUF_CNT; |
| msgCnt--; |
| } else { |
| break; |
| } |
| } |
| } |
| |
| usb_fill_bulk_urb(urb, card->udev, |
| usb_rcvbulkpipe(card->udev, card->num_bulk_in), |
| urb->transfer_buffer, |
| urb->transfer_buffer_length, |
| cpcusb_read_bulk_callback, card); |
| |
| retval = usb_submit_urb(urb, GFP_ATOMIC); |
| |
| if (retval) { |
| err("%s - failed resubmitting read urb, error %d", __func__, retval); |
| } |
| |
| spin_unlock_irqrestore(&card->slock, flags); |
| |
| wake_up_interruptible(card->chan->CPCWait_q); |
| } |
| |
| /* |
| * callback for bulk IN urb |
| */ |
| static void cpcusb_write_bulk_callback(struct urb *urb) |
| { |
| CPC_USB_T *card = (CPC_USB_T *) urb->context; |
| unsigned long flags; |
| int j; |
| |
| spin_lock_irqsave(&card->slock, flags); |
| |
| /* find this urb */ |
| for (j = 0; j < CPC_USB_URB_CNT; j++) { |
| if (card->wrUrbs[j].urb == urb) { |
| dbg("URB found no. %d", j); |
| /* notify anyone waiting that the write has finished */ |
| complete(&card->wrUrbs[j].finished); |
| atomic_set(&card->wrUrbs[j].busy, 0); |
| break; |
| } |
| } |
| |
| switch (urb->status) { |
| case 0: /* success */ |
| break; |
| case -ECONNRESET: |
| case -ENOENT: |
| case -ESHUTDOWN: |
| /* urb was killed */ |
| spin_unlock_irqrestore(&card->slock, flags); |
| dbg("%s - write urb no. %d killed", __func__, j); |
| return; |
| default: |
| info("%s - nonzero urb status %d", __func__, urb->status); |
| break; |
| } |
| |
| spin_unlock_irqrestore(&card->slock, flags); |
| |
| wake_up_interruptible(card->chan->CPCWait_q); |
| } |
| |
| static inline int cpcusb_get_free_slot(void) |
| { |
| int i; |
| |
| for (i = 0; i < CPC_USB_CARD_CNT; i++) { |
| if (!CPCUSB_Table[i]) |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| /* |
| * probe function for new CPC-USB devices |
| */ |
| static int cpcusb_probe(struct usb_interface *interface, |
| const struct usb_device_id *id) |
| { |
| CPC_USB_T *card = NULL; |
| CPC_CHAN_T *chan = NULL; |
| |
| struct usb_device *udev = interface_to_usbdev(interface); |
| struct usb_host_interface *iface_desc; |
| struct usb_endpoint_descriptor *endpoint; |
| |
| int i, j, retval = -ENOMEM, slot; |
| |
| slot = cpcusb_get_free_slot(); |
| if (slot < 0) { |
| info("No more devices supported"); |
| return -ENOMEM; |
| } |
| |
| /* allocate memory for our device state and initialize it */ |
| card = kzalloc(sizeof(CPC_USB_T), GFP_KERNEL); |
| if (!card) { |
| err("Out of memory"); |
| return -ENOMEM; |
| } |
| CPCUSB_Table[slot] = card; |
| |
| /* allocate and initialize the channel struct */ |
| card->chan = kmalloc(sizeof(CPC_CHAN_T), GFP_KERNEL); |
| if (!card->chan) { |
| kfree(card); |
| err("Out of memory"); |
| return -ENOMEM; |
| } |
| |
| chan = card->chan; |
| memset(chan, 0, sizeof(CPC_CHAN_T)); |
| ResetBuffer(chan); |
| |
| init_MUTEX(&card->sem); |
| spin_lock_init(&card->slock); |
| |
| card->udev = udev; |
| card->interface = interface; |
| if (udev->descriptor.iSerialNumber) { |
| usb_string(udev, udev->descriptor.iSerialNumber, card->serialNumber, |
| 128); |
| info("Serial %s", card->serialNumber); |
| } |
| |
| card->productId = udev->descriptor.idProduct; |
| info("Product %s", |
| card->productId == USB_CPCUSB_LPC2119_PRODUCT_ID ? |
| "CPC-USB/ARM7" : "CPC-USB/M16C"); |
| |
| /* set up the endpoint information */ |
| /* check out the endpoints */ |
| /* use only the first bulk-in and bulk-out endpoints */ |
| iface_desc = &interface->altsetting[0]; |
| for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { |
| endpoint = &iface_desc->endpoint[i].desc; |
| |
| if (!card->num_intr_in && |
| (endpoint->bEndpointAddress & USB_DIR_IN) && |
| ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) |
| == USB_ENDPOINT_XFER_INT)) { |
| card->intr_in_urb = usb_alloc_urb(0, GFP_KERNEL); |
| card->num_intr_in = 1; |
| |
| if (!card->intr_in_urb) { |
| err("No free urbs available"); |
| goto error; |
| } |
| |
| dbg("intr_in urb %d", card->num_intr_in); |
| } |
| |
| if (!card->num_bulk_in && |
| (endpoint->bEndpointAddress & USB_DIR_IN) && |
| ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) |
| == USB_ENDPOINT_XFER_BULK)) { |
| card->num_bulk_in = 2; |
| for (j = 0; j < CPC_USB_URB_CNT; j++) { |
| card->urbs[j].size = endpoint->wMaxPacketSize; |
| card->urbs[j].urb = usb_alloc_urb(0, GFP_KERNEL); |
| if (!card->urbs[j].urb) { |
| err("No free urbs available"); |
| goto error; |
| } |
| card->urbs[j].buffer = |
| usb_buffer_alloc(udev, |
| card->urbs[j].size, |
| GFP_KERNEL, |
| &card->urbs[j].urb->transfer_dma); |
| if (!card->urbs[j].buffer) { |
| err("Couldn't allocate bulk_in_buffer"); |
| goto error; |
| } |
| } |
| info("%s - %d reading URB's allocated", |
| __func__, CPC_USB_URB_CNT); |
| } |
| |
| if (!card->num_bulk_out && |
| !(endpoint->bEndpointAddress & USB_DIR_IN) && |
| ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) |
| == USB_ENDPOINT_XFER_BULK)) { |
| |
| card->num_bulk_out = 2; |
| |
| for (j = 0; j < CPC_USB_URB_CNT; j++) { |
| card->wrUrbs[j].size = |
| endpoint->wMaxPacketSize; |
| card->wrUrbs[j].urb = |
| usb_alloc_urb(0, GFP_KERNEL); |
| if (!card->wrUrbs[j].urb) { |
| err("No free urbs available"); |
| goto error; |
| } |
| card->wrUrbs[j].buffer = usb_buffer_alloc(udev, |
| card->wrUrbs[j].size, GFP_KERNEL, |
| &card->wrUrbs[j].urb->transfer_dma); |
| |
| if (!card->wrUrbs[j].buffer) { |
| err("Couldn't allocate bulk_out_buffer"); |
| goto error; |
| } |
| |
| usb_fill_bulk_urb(card->wrUrbs[j].urb, udev, |
| usb_sndbulkpipe(udev, endpoint->bEndpointAddress), |
| card->wrUrbs[j].buffer, |
| card->wrUrbs[j].size, |
| cpcusb_write_bulk_callback, |
| card); |
| } |
| |
| info("%s - %d writing URB's allocated", __func__, CPC_USB_URB_CNT); |
| } |
| } |
| |
| if (!(card->num_bulk_in && card->num_bulk_out)) { |
| err("Couldn't find both bulk-in and bulk-out endpoints"); |
| goto error; |
| } |
| |
| /* allow device read, write and ioctl */ |
| card->present = 1; |
| |
| /* we can register the device now, as it is ready */ |
| usb_set_intfdata(interface, card); |
| retval = usb_register_dev(interface, &cpcusb_class); |
| |
| if (retval) { |
| /* something prevented us from registering this driver */ |
| err("Not able to get a minor for this device."); |
| usb_set_intfdata(interface, NULL); |
| goto error; |
| } |
| |
| card->chan->minor = card->minor = interface->minor; |
| |
| chan->buf = vmalloc(sizeof(CPC_MSG_T) * CPC_MSG_BUF_CNT); |
| if (chan->buf == NULL) { |
| err("Out of memory"); |
| retval = -ENOMEM; |
| goto error; |
| } |
| info("Allocated memory for %d messages (%lu kbytes)", |
| CPC_MSG_BUF_CNT, (long unsigned int)(sizeof(CPC_MSG_T) * CPC_MSG_BUF_CNT) / 1000); |
| memset(chan->buf, 0, sizeof(CPC_MSG_T) * CPC_MSG_BUF_CNT); |
| |
| ResetBuffer(chan); |
| |
| card->chan->CPCWait_q = kmalloc(sizeof(wait_queue_head_t), GFP_KERNEL); |
| if (!card->chan->CPCWait_q) { |
| err("Out of memory"); |
| retval = -ENOMEM; |
| goto error; |
| } |
| init_waitqueue_head(card->chan->CPCWait_q); |
| |
| CPCUSB_Table[slot] = card; |
| card->idx = slot; |
| CPCUsbCnt++; |
| |
| /* let the user know what node this device is now attached to */ |
| info("Device now attached to USB-%d", card->minor); |
| return 0; |
| |
| error: |
| for (j = 0; j < CPC_USB_URB_CNT; j++) { |
| if (card->urbs[j].buffer) { |
| usb_buffer_free(card->udev, card->urbs[j].size, |
| card->urbs[j].buffer, |
| card->urbs[j].urb->transfer_dma); |
| card->urbs[j].buffer = NULL; |
| } |
| if (card->urbs[j].urb) { |
| usb_free_urb(card->urbs[j].urb); |
| card->urbs[j].urb = NULL; |
| } |
| } |
| |
| cpcusb_delete(card); |
| return retval; |
| } |
| |
| /* |
| * called by the usb core when the device is removed from the system |
| */ |
| static void cpcusb_disconnect(struct usb_interface *interface) |
| { |
| CPC_USB_T *card = NULL; |
| int minor, j; |
| |
| /* prevent races with open() */ |
| down(&disconnect_sem); |
| |
| card = usb_get_intfdata(interface); |
| usb_set_intfdata(interface, NULL); |
| |
| down(&card->sem); |
| |
| /* prevent device read, write and ioctl */ |
| card->present = 0; |
| |
| minor = card->minor; |
| |
| /* free all urbs and their buffers */ |
| for (j = 0; j < CPC_USB_URB_CNT; j++) { |
| /* terminate an ongoing write */ |
| if (atomic_read(&card->wrUrbs[j].busy)) { |
| usb_kill_urb(card->wrUrbs[j].urb); |
| wait_for_completion(&card->wrUrbs[j].finished); |
| } |
| usb_buffer_free(card->udev, card->wrUrbs[j].size, |
| card->wrUrbs[j].buffer, |
| card->wrUrbs[j].urb->transfer_dma); |
| usb_free_urb(card->wrUrbs[j].urb); |
| } |
| info("%d write URBs freed", CPC_USB_URB_CNT); |
| |
| /* free all urbs and their buffers */ |
| for (j = 0; j < CPC_USB_URB_CNT; j++) { |
| usb_buffer_free(card->udev, card->urbs[j].size, |
| card->urbs[j].buffer, |
| card->urbs[j].urb->transfer_dma); |
| usb_free_urb(card->urbs[j].urb); |
| } |
| info("%d read URBs freed", CPC_USB_URB_CNT); |
| usb_free_urb(card->intr_in_urb); |
| |
| /* give back our minor */ |
| usb_deregister_dev(interface, &cpcusb_class); |
| |
| up(&card->sem); |
| |
| /* if the device is opened, cpcusb_release will clean this up */ |
| if (!card->open) |
| cpcusb_delete(card); |
| else |
| wake_up_interruptible(card->chan->CPCWait_q); |
| |
| up(&disconnect_sem); |
| |
| CPCUsbCnt--; |
| info("USB-%d now disconnected", minor); |
| } |
| |
| static int __init CPCUsb_Init(void) |
| { |
| int result, i; |
| |
| info(DRIVER_DESC " v" DRIVER_VERSION); |
| info("Build on " __DATE__ " at " __TIME__); |
| |
| for (i = 0; i < CPC_USB_CARD_CNT; i++) |
| CPCUSB_Table[i] = 0; |
| |
| /* register this driver with the USB subsystem */ |
| result = usb_register(&cpcusb_driver); |
| if (result) { |
| err("usb_register failed. Error number %d", result); |
| return result; |
| } |
| |
| procDir = proc_mkdir(CPC_USB_PROC_DIR, NULL); |
| if (!procDir) { |
| err("Could not create proc entry"); |
| } else { |
| procEntry = create_proc_read_entry("info", 0444, procDir, |
| cpcusb_proc_read_info, |
| NULL); |
| if (!procEntry) { |
| err("Could not create proc entry %s", CPC_USB_PROC_DIR "/info"); |
| remove_proc_entry(CPC_USB_PROC_DIR, NULL); |
| procDir = NULL; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void __exit CPCUsb_Exit(void) |
| { |
| wait_event(rmmodWq, !atomic_read(&useCount)); |
| |
| /* deregister this driver with the USB subsystem */ |
| usb_deregister(&cpcusb_driver); |
| |
| if (procDir) { |
| if (procEntry) |
| remove_proc_entry("info", procDir); |
| remove_proc_entry(CPC_USB_PROC_DIR, NULL); |
| } |
| } |
| |
| module_init(CPCUsb_Init); |
| module_exit(CPCUsb_Exit); |