| /* |
| * f_acm.c -- USB CDC serial (ACM) function driver |
| * |
| * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) |
| * Copyright (C) 2008 by David Brownell |
| * Copyright (C) 2008 by Nokia Corporation |
| * Copyright (C) 2009 by Samsung Electronics |
| * Author: Michal Nazarewicz (mina86@mina86.com) |
| * |
| * This software is distributed under the terms of the GNU General |
| * Public License ("GPL") as published by the Free Software Foundation, |
| * either version 2 of that License or (at your option) any later version. |
| */ |
| |
| /* #define VERBOSE_DEBUG */ |
| |
| #include <linux/slab.h> |
| #include <linux/kernel.h> |
| #include <linux/device.h> |
| #include <linux/usb/composite.h> |
| #include <linux/workqueue.h> |
| |
| #include "pxa910_u_serial.h" |
| #include "gadget_chips.h" |
| |
| /* |
| * This CDC ACM function support just wraps control functions and |
| * notifications around the generic serial-over-usb code. |
| * |
| * Because CDC ACM is standardized by the USB-IF, many host operating |
| * systems have drivers for it. Accordingly, ACM is the preferred |
| * interop solution for serial-port type connections. The control |
| * models are often not necessary, and in any case don't do much in |
| * this bare-bones implementation. |
| * |
| * Note that even MS-Windows has some support for ACM. However, that |
| * support is somewhat broken because when you use ACM in a composite |
| * device, having multiple interfaces confuses the poor OS. It doesn't |
| * seem to understand CDC Union descriptors. The new "association" |
| * descriptors (roughly equivalent to CDC Unions) may sometimes help. |
| */ |
| #define GS_DIAG_PORT_BASE 1 |
| |
| struct pxa910_f_diag { |
| struct pxa910_gserial port; |
| u8 data_id; |
| u8 port_num; |
| }; |
| |
| static inline struct pxa910_f_diag *pxa910_func_to_diag(struct usb_function *f) |
| { |
| return container_of(f, struct pxa910_f_diag, port.func); |
| } |
| |
| static inline struct |
| pxa910_f_diag *pxa910_port_to_diag(struct pxa910_gserial *p) |
| { |
| return container_of(p, struct pxa910_f_diag, port); |
| } |
| |
| /*-------------------------------------------------------------------------*/ |
| |
| /* interface descriptor: */ |
| static struct usb_interface_descriptor pxa910_diag_interface_desc = { |
| .bLength = USB_DT_INTERFACE_SIZE, |
| .bDescriptorType = USB_DT_INTERFACE, |
| /* .bInterfaceNumber = DYNAMIC */ |
| .bNumEndpoints = 2, |
| .bInterfaceClass = USB_CLASS_CDC_DATA, |
| .bInterfaceSubClass = 0, |
| .bInterfaceProtocol = 0xff, |
| /* .iInterface = DYNAMIC */ |
| }; |
| |
| /* full speed support: */ |
| |
| static struct usb_endpoint_descriptor pxa910_diag_fs_in_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_DIR_IN, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| }; |
| |
| static struct usb_endpoint_descriptor pxa910_diag_fs_out_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_DIR_OUT, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| }; |
| |
| static struct usb_descriptor_header *pxa910_diag_fs_function[] = { |
| (struct usb_descriptor_header *)&pxa910_diag_interface_desc, |
| (struct usb_descriptor_header *)&pxa910_diag_fs_in_desc, |
| (struct usb_descriptor_header *)&pxa910_diag_fs_out_desc, |
| NULL, |
| }; |
| |
| /* high speed support: */ |
| |
| static struct usb_endpoint_descriptor pxa910_diag_hs_in_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| .wMaxPacketSize = cpu_to_le16(512), |
| }; |
| |
| static struct usb_endpoint_descriptor pxa910_diag_hs_out_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| .wMaxPacketSize = cpu_to_le16(512), |
| }; |
| |
| static struct usb_descriptor_header *pxa910_diag_hs_function[] = { |
| (struct usb_descriptor_header *)&pxa910_diag_interface_desc, |
| (struct usb_descriptor_header *)&pxa910_diag_hs_in_desc, |
| (struct usb_descriptor_header *)&pxa910_diag_hs_out_desc, |
| NULL, |
| }; |
| |
| static struct usb_endpoint_descriptor pxa910_diag_ss_in_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| .wMaxPacketSize = cpu_to_le16(1024), |
| }; |
| |
| static struct usb_endpoint_descriptor pxa910_diag_ss_out_desc = { |
| .bLength = USB_DT_ENDPOINT_SIZE, |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bmAttributes = USB_ENDPOINT_XFER_BULK, |
| .wMaxPacketSize = cpu_to_le16(1024), |
| }; |
| |
| static struct usb_ss_ep_comp_descriptor pxa910_diag_ss_bulk_comp_desc = { |
| .bLength = sizeof(pxa910_diag_ss_bulk_comp_desc), |
| .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, |
| }; |
| |
| static struct usb_descriptor_header *pxa910_diag_ss_function[] = { |
| (struct usb_descriptor_header *) &pxa910_diag_interface_desc, |
| (struct usb_descriptor_header *) &pxa910_diag_ss_in_desc, |
| (struct usb_descriptor_header *) &pxa910_diag_ss_bulk_comp_desc, |
| (struct usb_descriptor_header *) &pxa910_diag_ss_out_desc, |
| (struct usb_descriptor_header *) &pxa910_diag_ss_bulk_comp_desc, |
| NULL, |
| }; |
| |
| /* string descriptors: */ |
| |
| /* static strings, in UTF-8 */ |
| static struct usb_string pxa910_diag_string_defs[] = { |
| [0].s = "Marvell DIAG", |
| { /* ZEROES END LIST */ }, |
| }; |
| |
| static struct usb_gadget_strings pxa910_diag_string_table = { |
| .language = 0x0409, /* en-us */ |
| .strings = pxa910_diag_string_defs, |
| }; |
| |
| static struct usb_gadget_strings *pxa910_diag_strings[] = { |
| &pxa910_diag_string_table, |
| NULL, |
| }; |
| |
| /*-------------------------------------------------------------------------*/ |
| |
| static int |
| pxa910_diag_set_alt(struct usb_function *f, unsigned intf, unsigned alt) |
| { |
| struct pxa910_f_diag *diag = pxa910_func_to_diag(f); |
| struct usb_composite_dev *cdev = f->config->cdev; |
| |
| /* we know alt == 0, so this is an activation or a reset */ |
| |
| if (diag->port.in->driver_data) { |
| DBG(cdev, "reset diag ttyGS%d\n", diag->port_num); |
| pxa910_gserial_disconnect(&diag->port); |
| } else { |
| DBG(cdev, "activate generic ttyGS%d\n", diag->port_num); |
| } |
| if (!diag->port.in->desc || !diag->port.out->desc) { |
| DBG(cdev, "activate diag ttyGS%d\n", diag->port_num); |
| if (config_ep_by_speed(cdev->gadget, f, diag->port.in) || |
| config_ep_by_speed(cdev->gadget, f, diag->port.out)) { |
| diag->port.in->desc = NULL; |
| diag->port.out->desc = NULL; |
| return -EINVAL; |
| } |
| } |
| |
| pxa910_gserial_connect(&diag->port, diag->port_num); |
| |
| return 0; |
| } |
| |
| static void pxa910_diag_disable(struct usb_function *f) |
| { |
| struct pxa910_f_diag *diag = pxa910_func_to_diag(f); |
| struct usb_composite_dev *cdev = f->config->cdev; |
| |
| DBG(cdev, "diag ttyGS%d deactivated\n", diag->port_num); |
| |
| pxa910_gserial_disconnect(&diag->port); |
| } |
| |
| /*-------------------------------------------------------------------------*/ |
| |
| /* DIAG function driver setup/binding */ |
| static int |
| pxa910_diag_bind(struct usb_configuration *c, struct usb_function *f) |
| { |
| struct usb_composite_dev *cdev = c->cdev; |
| struct pxa910_f_diag *diag = pxa910_func_to_diag(f); |
| int status; |
| struct usb_ep *ep; |
| |
| /* allocate instance-specific interface IDs, and patch descriptors */ |
| status = usb_interface_id(c, f); |
| if (status < 0) |
| goto fail; |
| diag->data_id = status; |
| |
| pxa910_diag_interface_desc.bInterfaceNumber = status; |
| |
| status = -ENODEV; |
| |
| /* allocate instance-specific endpoints */ |
| ep = usb_ep_autoconfig(cdev->gadget, &pxa910_diag_fs_in_desc); |
| if (!ep) |
| goto fail; |
| diag->port.in = ep; |
| ep->driver_data = cdev; /* claim */ |
| |
| ep = usb_ep_autoconfig(cdev->gadget, &pxa910_diag_fs_out_desc); |
| if (!ep) |
| goto fail; |
| diag->port.out = ep; |
| ep->driver_data = cdev; /* claim */ |
| |
| /* copy descriptors */ |
| f->fs_descriptors = usb_copy_descriptors(pxa910_diag_fs_function); |
| if (!f->fs_descriptors) |
| goto fail; |
| |
| /* support all relevant hardware speeds... we expect that when |
| * hardware is dual speed, all bulk-capable endpoints work at |
| * both speeds |
| */ |
| if (gadget_is_dualspeed(c->cdev->gadget)) { |
| pxa910_diag_hs_in_desc.bEndpointAddress = |
| pxa910_diag_fs_in_desc.bEndpointAddress; |
| pxa910_diag_hs_out_desc.bEndpointAddress = |
| pxa910_diag_fs_out_desc.bEndpointAddress; |
| |
| /* copy descriptors */ |
| f->hs_descriptors = |
| usb_copy_descriptors(pxa910_diag_hs_function); |
| } |
| if (gadget_is_superspeed(c->cdev->gadget)) { |
| pxa910_diag_ss_in_desc.bEndpointAddress = |
| pxa910_diag_fs_in_desc.bEndpointAddress; |
| pxa910_diag_ss_out_desc.bEndpointAddress = |
| pxa910_diag_fs_out_desc.bEndpointAddress; |
| |
| /* copy descriptors, and track endpoint copies */ |
| f->ss_descriptors = |
| usb_copy_descriptors(pxa910_diag_ss_function); |
| if (!f->ss_descriptors) |
| goto fail; |
| } |
| |
| DBG(cdev, "diag ttyGS%d: %s speed IN/%s OUT/%s\n", |
| diag->port_num, |
| gadget_is_superspeed(c->cdev->gadget) ? "super" : |
| gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", |
| diag->port.in->name, diag->port.out->name); |
| return 0; |
| |
| fail: |
| /* we might as well release our claims on endpoints */ |
| if (diag->port.out) |
| diag->port.out->driver_data = NULL; |
| if (diag->port.in) |
| diag->port.in->driver_data = NULL; |
| |
| ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status); |
| |
| return status; |
| } |
| |
| static void |
| pxa910_diag_unbind(struct usb_configuration *c, struct usb_function *f) |
| { |
| struct pxa910_f_diag *diag = pxa910_func_to_diag(f); |
| |
| if (gadget_is_dualspeed(c->cdev->gadget)) |
| usb_free_descriptors(f->hs_descriptors); |
| if (gadget_is_superspeed(c->cdev->gadget)) |
| usb_free_descriptors(f->ss_descriptors); |
| usb_free_descriptors(f->fs_descriptors); |
| kfree(diag); |
| } |
| |
| /** |
| * acm_bind_config - add a CDC ACM function to a configuration |
| * @c: the configuration to support the CDC ACM instance |
| * @port_num: /dev/ttyGS* port this interface will use |
| * Context: single threaded during gadget setup |
| * |
| * Returns zero on success, else negative errno. |
| * |
| * Caller must have called @gserial_setup() with enough ports to |
| * handle all the ones it binds. Caller is also responsible |
| * for calling @gserial_cleanup() before module unload. |
| */ |
| int pxa910_diag_bind_config(struct usb_configuration *c, u8 port_num) |
| { |
| struct pxa910_f_diag *diag; |
| int status; |
| |
| /* REVISIT might want instance-specific strings to help |
| * distinguish instances ... |
| */ |
| |
| /* maybe allocate device-global string IDs, and patch descriptors */ |
| if (pxa910_diag_string_defs[0].id == 0) { |
| status = usb_string_id(c->cdev); |
| if (status < 0) |
| return status; |
| |
| pxa910_diag_string_defs[0].id = status; |
| |
| pxa910_diag_interface_desc.iInterface = status; |
| } |
| |
| /* allocate and initialize one new instance */ |
| diag = kzalloc(sizeof(*diag), GFP_KERNEL); |
| if (!diag) |
| return -ENOMEM; |
| |
| diag->port_num = port_num; |
| |
| diag->port.func.name = "diag"; |
| diag->port.func.strings = pxa910_diag_strings; |
| /* descriptors are per-instance copies */ |
| diag->port.func.bind = pxa910_diag_bind; |
| diag->port.func.unbind = pxa910_diag_unbind; |
| diag->port.func.set_alt = pxa910_diag_set_alt; |
| diag->port.func.disable = pxa910_diag_disable; |
| |
| init_waitqueue_head(&diag->port.port_send); |
| status = usb_add_function(c, &diag->port.func); |
| if (status) |
| kfree(diag); |
| return status; |
| } |
| |
| static int gs_marvell_diag_write(struct tty_struct *tty, |
| const unsigned char *buf, int count) |
| { |
| struct pxa910_gs_port *port = tty->driver_data; |
| unsigned long flags; |
| int status; |
| |
| pr_vdebug("gs_marvell_diag_write: ttyGS%d (%p) writing %d bytes\n", |
| port->port_num, tty, count); |
| |
| if (port == NULL) |
| goto out; |
| spin_lock_irqsave(&port->port_lock, flags); |
| if (count) |
| count = pxa910_gs_buf_put(&port->port_write_buf, buf, count); |
| |
| /* treat count == 0 as flush_chars() */ |
| if (port->port_usb) |
| status = pxa910_gs_start_tx(port); |
| spin_unlock_irqrestore(&port->port_lock, flags); |
| out: |
| if (count == 0) |
| return -EAGAIN; |
| return count; |
| } |
| |
| int gs_marvell_diag_send(const unsigned char *buf, int count) |
| { |
| struct tty_struct *tty; |
| struct pxa910_gs_port *port; |
| int c, retval = 0; |
| const unsigned char *b = buf; |
| unsigned long flags; |
| wait_queue_head_t *p_port_send; |
| |
| /* Assume that we have only one port */ |
| port = pxa910_ports[GS_DIAG_PORT_BASE].port; |
| tty = port->port.tty; |
| |
| if (tty == NULL || tty->driver_data == NULL || port->port_usb == NULL) |
| return count; |
| while (count > 0) { |
| c = gs_marvell_diag_write(tty, b, count); |
| |
| if (c == count) { |
| b += c; |
| break; /* send all bytes successfully */ |
| } else if (c >= 0) { |
| b += c; |
| count -= c; |
| continue; |
| } else if (c < 0) { |
| retval = c; |
| if (c != -EAGAIN) |
| goto break_out; |
| } |
| |
| /* retry to wait enough buffer to send the rest bytes. */ |
| spin_lock_irqsave(&port->port_lock, flags); |
| |
| if (port->port_usb) { |
| p_port_send = &port->port_usb->port_send; |
| } else { |
| p_port_send = NULL; |
| pr_info("%s: usb port is released.\n", |
| __func__); |
| } |
| |
| spin_unlock_irqrestore(&port->port_lock, flags); |
| |
| if (p_port_send) |
| interruptible_sleep_on_timeout(p_port_send, |
| 10 * HZ / 1000); |
| |
| port = pxa910_ports[GS_DIAG_PORT_BASE].port; |
| tty = port->port.tty; |
| if (tty == NULL || tty->driver_data == NULL |
| || port->port_usb == NULL) |
| return count; |
| } |
| break_out: |
| return (b - buf) ? b - buf : retval; |
| } |
| EXPORT_SYMBOL(gs_marvell_diag_send); |
| |
| typedef int (*marvell_diag_rx_callback) (char *packet, int len); |
| |
| marvell_diag_rx_callback gs_marvell_diag_rx_callback = |
| (marvell_diag_rx_callback) NULL; |
| EXPORT_SYMBOL(gs_marvell_diag_rx_callback); |
| |
| typedef int (*marvell_diag_ioctl) (unsigned int cmd, unsigned long arg); |
| |
| marvell_diag_ioctl gs_marvell_diag_ioctl = (marvell_diag_ioctl) NULL; |
| EXPORT_SYMBOL(gs_marvell_diag_ioctl); |
| |
| /* |
| * RX tasklet takes data out of the RX queue and hands it up to the TTY |
| * layer until it refuses to take any more data (or is throttled back). |
| * Then it issues reads for any further data. |
| * |
| * If the RX queue becomes full enough that no usb_request is queued, |
| * the OUT endpoint may begin NAKing as soon as its FIFO fills up. |
| * So QUEUE_SIZE packets plus however many the FIFO holds (usually two) |
| * can be buffered before the TTY layer's buffers (currently 64 KB). |
| */ |
| static void gs_marvell_diag_rx_push(unsigned long _port) |
| { |
| struct pxa910_gs_port *port = (void *)_port; |
| struct tty_struct *tty; |
| struct list_head *queue = &port->read_queue; |
| bool disconnect = false; |
| bool do_push = false; |
| struct timespec now; |
| |
| /* hand any queued data to the tty */ |
| spin_lock_irq(&port->port_lock); |
| tty = port->port.tty; |
| while (!list_empty(queue)) { |
| struct usb_request *req; |
| |
| now = CURRENT_TIME; |
| req = list_first_entry(queue, struct usb_request, list); |
| |
| /* discard data if tty was closed */ |
| if (!tty) |
| goto recycle; |
| |
| /* leave data queued if tty was rx throttled */ |
| if (test_bit(TTY_THROTTLED, &tty->flags)) |
| break; |
| |
| switch (req->status) { |
| case -ESHUTDOWN: |
| disconnect = true; |
| pr_vdebug(PREFIX "%d: shutdown\n", port->port_num); |
| break; |
| |
| default: |
| /* presumably a transient fault */ |
| pr_warn("%d: unexpected RX status %d\n", |
| port->port_num, req->status); |
| /* FALLTHROUGH */ |
| case 0: |
| /* normal completion */ |
| break; |
| } |
| |
| /* push data to (open) tty */ |
| if (req->actual) { |
| char *packet = req->buf; |
| unsigned size = req->actual; |
| unsigned n; |
| int count; |
| |
| /* we may have pushed part of this packet already... */ |
| n = port->n_read; |
| if (n) { |
| packet += n; |
| size -= n; |
| } |
| |
| if (gs_marvell_diag_rx_callback != |
| (marvell_diag_rx_callback) NULL) { |
| int filtered; |
| filtered = |
| gs_marvell_diag_rx_callback(packet, size); |
| if (filtered) |
| goto recycle; |
| } |
| |
| count = tty_insert_flip_string(tty->port, packet, size); |
| if (count) |
| do_push = true; |
| if (count != size) { |
| /* stop pushing; TTY layer can't handle more */ |
| port->n_read += count; |
| pr_vdebug(PREFIX "%d: rx block %d/%d\n", |
| port->port_num, count, req->actual); |
| break; |
| } |
| port->n_read = 0; |
| } |
| recycle: |
| list_move(&req->list, &port->read_pool); |
| port->read_started--; |
| } |
| |
| /* Push from tty to ldisc; this is immediate with low_latency, and |
| * may trigger callbacks to this driver ... so drop the spinlock. |
| */ |
| if (tty && do_push) { |
| spin_unlock_irq(&port->port_lock); |
| tty_flip_buffer_push(tty->port); |
| wake_up_interruptible(&tty->read_wait); |
| spin_lock_irq(&port->port_lock); |
| |
| /* tty may have been closed */ |
| tty = port->port.tty; |
| } |
| |
| /* We want our data queue to become empty ASAP, keeping data |
| * in the tty and ldisc (not here). If we couldn't push any |
| * this time around, there may be trouble unless there's an |
| * implicit tty_unthrottle() call on its way... |
| * |
| * REVISIT we should probably add a timer to keep the tasklet |
| * from starving ... but it's not clear that case ever happens. |
| */ |
| if (!list_empty(queue) && tty) { |
| if (!test_bit(TTY_THROTTLED, &tty->flags)) { |
| if (do_push) |
| tasklet_schedule(&port->push); |
| else |
| pr_warn("%d: RX not scheduled?\n", |
| port->port_num); |
| } |
| } |
| |
| /* If we're still connected, refill the USB RX queue. */ |
| if (!disconnect && port->port_usb) |
| pxa910_gs_start_rx(port); |
| |
| spin_unlock_irq(&port->port_lock); |
| } |
| |
| static int gs_marvell_diag_port_alloc(unsigned port_num, |
| struct usb_cdc_line_coding *coding) |
| { |
| struct pxa910_gs_port *port; |
| |
| port = kzalloc(sizeof(struct pxa910_gs_port), GFP_KERNEL); |
| if (port == NULL) |
| return -ENOMEM; |
| |
| tty_port_init(&port->port); |
| spin_lock_init(&port->port_lock); |
| init_waitqueue_head(&port->drain_wait); |
| |
| tasklet_init(&port->push, gs_marvell_diag_rx_push, (unsigned long)port); |
| |
| INIT_LIST_HEAD(&port->read_pool); |
| INIT_LIST_HEAD(&port->read_queue); |
| INIT_LIST_HEAD(&port->write_pool); |
| |
| port->port_num = port_num; |
| port->port_line_coding = *coding; |
| port->tx_wq = create_singlethread_workqueue("diag_gadget_tx"); |
| INIT_WORK(&port->tx_work, pxa910_gs_tx_worker); |
| pxa910_ports[port_num].port = port; |
| |
| return 0; |
| } |
| |
| #ifdef DIAG_USB_TEST |
| static void diagUsbTest3(void) |
| { |
| int i; |
| int ret; |
| unsigned long start, end, timeuse; |
| |
| memset(testbuf, 'b', sizeof(testbuf)); |
| pr_info("diag usb test start...\n"); |
| msleep_interruptible(1000); |
| |
| start = jiffies; |
| for (i = 0; i < lpcunt; i++) { |
| ret = gs_usb_write(gs_diag_tty, testbuf, sizeof(testbuf)); |
| if (ret != sizeof(testbuf)) |
| pr_info("usb write error!ret=%d\n", ret); |
| |
| while (!writecomplete) { |
| if (wait_event_interruptible(wcwaitQ, |
| writecomplete == 1)) |
| continue; |
| } |
| writecomplete = 0; |
| } |
| end = jiffies; |
| timeuse = (end - start) * 1000 / HZ; |
| pr_info("sending %d Kbytes to use take %d ms\n", |
| lpcunt * sizeof(testbuf) / 1024, timeuse); |
| } |
| |
| static void diagUsbTest(int caseNo) |
| { |
| |
| init_waitqueue_head(&wcwaitQ); |
| switch (caseNo) { |
| case 1: |
| /* diagUsbTest1(); */ |
| break; |
| case 2: |
| /* diagUsbTest2(); */ |
| break; |
| case 3: |
| diagUsbTest3(); |
| break; |
| } |
| } |
| #endif |
| |
| /* |
| * gs_ioctl |
| */ |
| static int pxa910_diag_ioctl(struct tty_struct *tty, unsigned int cmd, |
| unsigned long arg) |
| { |
| struct pxa910_gs_port *port; |
| struct pxa910_f_diag *diag; |
| int rc = -ENOIOCTLCMD; |
| |
| if (tty == NULL) { |
| pr_err("%s cmd %u. call from context [%d:%d]: tty is NULL pointer\n", |
| __func__, cmd, current->pid, current->tgid); |
| return -EIO; |
| } |
| port = tty->driver_data; |
| if (port == NULL) { |
| pr_err("%s cmd %u. call from context [%d:%d]: tty->driver_data is NULL pointer\n", |
| __func__, cmd, current->pid, current->tgid); |
| return -EIO; |
| } |
| diag = pxa910_port_to_diag(port->port_usb); |
| if (diag == NULL) { |
| pr_err("%s cmd %u. call from context [%d:%d]: diag is NULL pointer\n", |
| __func__, cmd, current->pid, current->tgid); |
| return -EIO; |
| } |
| |
| /* handle ioctls */ |
| if (gs_marvell_diag_ioctl) |
| rc = gs_marvell_diag_ioctl(cmd, arg); |
| |
| return rc; |
| } |
| |
| static const struct tty_operations gs_marvell_diag_tty_ops = { |
| .open = pxa910_gs_open, |
| .close = pxa910_gs_close, |
| .write = gs_marvell_diag_write, |
| .put_char = pxa910_gs_put_char, |
| .flush_chars = pxa910_gs_flush_chars, |
| .write_room = pxa910_gs_write_room, |
| .chars_in_buffer = pxa910_gs_chars_in_buffer, |
| .ioctl = pxa910_diag_ioctl, |
| .unthrottle = pxa910_gs_unthrottle, |
| .break_ctl = pxa910_gs_break_ctl, |
| }; |
| |
| int pxa910_diag_gserial_setup(struct usb_gadget *g, unsigned count) |
| { |
| |
| #define GS_MARVELL_DIAG_MAJOR 126 |
| #define GS_MARVELL_DIAG_MINOR_START 16 |
| |
| unsigned i; |
| struct usb_cdc_line_coding coding; |
| int status; |
| |
| if (count == 0 || count > PXA910_N_PORTS) |
| return -EINVAL; |
| |
| gs_diag_tty_driver = alloc_tty_driver(count); |
| if (!gs_diag_tty_driver) |
| return -ENOMEM; |
| |
| gs_diag_tty_driver->owner = THIS_MODULE; |
| gs_diag_tty_driver->driver_name = "g_serial_diag"; |
| gs_diag_tty_driver->name = "ttydiag"; |
| /* uses dynamically assigned dev_t values */ |
| |
| gs_diag_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; |
| gs_diag_tty_driver->subtype = SERIAL_TYPE_NORMAL; |
| gs_diag_tty_driver->flags = |
| TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; |
| gs_diag_tty_driver->init_termios = tty_std_termios; |
| |
| /* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on |
| * MS-Windows. Otherwise, most of these flags shouldn't affect |
| * anything unless we were to actually hook up to a serial line. |
| */ |
| gs_diag_tty_driver->init_termios.c_cflag = |
| B9600 | CS8 | CREAD | HUPCL | CLOCAL; |
| gs_diag_tty_driver->init_termios.c_ispeed = 9600; |
| gs_diag_tty_driver->init_termios.c_ospeed = 9600; |
| |
| gs_diag_tty_driver->major = GS_MARVELL_DIAG_MAJOR; |
| gs_diag_tty_driver->minor_start = GS_MARVELL_DIAG_MINOR_START; |
| |
| coding.dwDTERate = cpu_to_le32(9600); |
| coding.bCharFormat = 8; |
| coding.bParityType = USB_CDC_NO_PARITY; |
| coding.bDataBits = USB_CDC_1_STOP_BITS; |
| |
| tty_set_operations(gs_diag_tty_driver, &gs_marvell_diag_tty_ops); |
| |
| /* make devices be openable */ |
| for (i = 0; i < count; i++) { |
| mutex_init(&pxa910_ports[GS_DIAG_PORT_BASE + i].lock); |
| status = |
| gs_marvell_diag_port_alloc(GS_DIAG_PORT_BASE + i, &coding); |
| if (status) { |
| count = i; |
| goto fail; |
| } |
| } |
| n_diag_ports = count; |
| |
| /* export the driver ... */ |
| status = tty_register_driver(gs_diag_tty_driver); |
| if (status) { |
| pr_err("%s: cannot register, err %d\n", __func__, status); |
| goto fail; |
| } |
| |
| /* ... and sysfs class devices, so mdev/udev make /dev/ttyGS* */ |
| for (i = 0; i < count; i++) { |
| struct device *tty_dev; |
| struct pxa910_gs_port *port = |
| pxa910_ports[GS_DIAG_PORT_BASE + i].port; |
| |
| tty_dev = tty_port_register_device(&port->port, |
| gs_diag_tty_driver, i, &g->dev); |
| if (IS_ERR(tty_dev)) |
| pr_warn("%s: no classdev for port %d, err %ld\n", |
| __func__, i, PTR_ERR(tty_dev)); |
| } |
| |
| pr_debug("%s: registered %d ttyGS* device%s\n", __func__, |
| count, (count == 1) ? "" : "s"); |
| |
| return status; |
| fail: |
| while (count--) |
| kfree(pxa910_ports[GS_DIAG_PORT_BASE + count].port); |
| put_tty_driver(gs_diag_tty_driver); |
| gs_diag_tty_driver = NULL; |
| return status; |
| } |
| |
| /** |
| * gserial_cleanup - remove TTY-over-USB driver and devices |
| * Context: may sleep |
| * |
| * This is called to free all resources allocated by @gserial_setup(). |
| * Accordingly, it may need to wait until some open /dev/ files have |
| * closed. |
| * |
| * The caller must have issued @gserial_disconnect() for any ports |
| * that had previously been connected, so that there is never any |
| * I/O pending when it's called. |
| */ |
| void pxa910_diag_gserial_cleanup(void) |
| { |
| unsigned i; |
| struct pxa910_gs_port *port; |
| |
| if (!gs_diag_tty_driver) |
| return; |
| |
| for (i = 0; i < n_diag_ports; i++) { |
| /* prevent new opens */ |
| mutex_lock(&pxa910_ports[GS_DIAG_PORT_BASE + i].lock); |
| port = pxa910_ports[GS_DIAG_PORT_BASE + i].port; |
| pxa910_ports[GS_DIAG_PORT_BASE + i].port = NULL; |
| mutex_unlock(&pxa910_ports[GS_DIAG_PORT_BASE + i].lock); |
| |
| tasklet_kill(&port->push); |
| destroy_workqueue(port->tx_wq); |
| /* wait for old opens to finish */ |
| wait_event(port->port.close_wait, pxa910_gs_closed(port)); |
| |
| WARN_ON(port->port_usb != NULL); |
| |
| tty_port_destroy(&port->port); |
| kfree(port); |
| |
| tty_unregister_device(gs_diag_tty_driver, i); |
| } |
| n_diag_ports = 0; |
| |
| tty_unregister_driver(gs_diag_tty_driver); |
| put_tty_driver(gs_diag_tty_driver); |
| gs_diag_tty_driver = NULL; |
| |
| pr_debug("%s: cleaned up ttyGS* support\n", __func__); |
| } |
| |
| |