/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include "cpc.h" #include "cpc_int.h" #include "cpcusb.h" #include "sja2m16c.h" /* Version Information */ #define DRIVER_AUTHOR "Sebastian Haas " #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);