From 43fc70279074a25b4fbd1d27d64c33a67c78272a Mon Sep 17 00:00:00 2001 From: Mayank Rana Date: Tue, 2 Feb 2016 18:28:57 -0800 Subject: usb: gadget: serial: Add snapshot of changes with f_serial Squash and apply following f_serial driver changes taken from msm-3.10 kernel as of commit ec18e1c5aed (Merge "mmc: card: set dma_mask as the queue bounce limit") d98217e USB: android gadget: queue the request only when serial is online b8bd483 USB: android gadget: Add interrupt ep and modem support in f_serial 7b56862 USB: Add super speed descriptors for android functions c5a7f7f gadget: u_serial: Add tiocmset/tiocmget functionality 2a821c8 usb: gadget: Add debug message to print the control line state information Signed-off-by: Mayank Rana --- drivers/usb/gadget/function/f_serial.c | 451 ++++++++++++++++++++++++++++++++- drivers/usb/gadget/function/u_serial.c | 86 ++++++- drivers/usb/gadget/function/u_serial.h | 10 + 3 files changed, 542 insertions(+), 5 deletions(-) (limited to 'drivers/usb/gadget') diff --git a/drivers/usb/gadget/function/f_serial.c b/drivers/usb/gadget/function/f_serial.c index 6bb44d613bab..8f98c1089e12 100644 --- a/drivers/usb/gadget/function/f_serial.c +++ b/drivers/usb/gadget/function/f_serial.c @@ -4,6 +4,7 @@ * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) * Copyright (C) 2008 by David Brownell * Copyright (C) 2008 by Nokia Corporation + * Copyright (c) 2013-2016 The Linux Foundation. All rights reserved. * * This software is distributed under the terms of the GNU General * Public License ("GPL") as published by the Free Software Foundation, @@ -14,6 +15,7 @@ #include #include #include +#include #include "u_serial.h" @@ -31,8 +33,35 @@ struct f_gser { struct gserial port; u8 data_id; u8 port_num; + u8 pending; + spinlock_t lock; + struct usb_ep *notify; + struct usb_request *notify_req; + struct usb_cdc_line_coding port_line_coding; + u8 online; + /* SetControlLineState request */ + u16 port_handshake_bits; +#define ACM_CTRL_RTS (1 << 1) /* unused with full duplex */ +#define ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */ + /* SerialState notification */ + u16 serial_state; +#define ACM_CTRL_OVERRUN (1 << 6) +#define ACM_CTRL_PARITY (1 << 5) +#define ACM_CTRL_FRAMING (1 << 4) +#define ACM_CTRL_RI (1 << 3) +#define ACM_CTRL_BRK (1 << 2) +#define ACM_CTRL_DSR (1 << 1) +#define ACM_CTRL_DCD (1 << 0) }; +static inline struct f_gser *port_to_gser(struct gserial *p) +{ + return container_of(p, struct f_gser, port); +} + +#define GS_LOG2_NOTIFY_INTERVAL 5 /* 1 << 5 == 32 msec */ +#define GS_NOTIFY_MAXPACKET 10 /* notification + 2 bytes */ + static inline struct f_gser *func_to_gser(struct usb_function *f) { return container_of(f, struct f_gser, port.func); @@ -46,15 +75,55 @@ static struct usb_interface_descriptor gser_interface_desc = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, /* .bInterfaceNumber = DYNAMIC */ - .bNumEndpoints = 2, + .bNumEndpoints = 3, .bInterfaceClass = USB_CLASS_VENDOR_SPEC, .bInterfaceSubClass = 0, .bInterfaceProtocol = 0, /* .iInterface = DYNAMIC */ }; +static struct usb_cdc_header_desc gser_header_desc = { + .bLength = sizeof(gser_header_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_call_mgmt_descriptor + gser_call_mgmt_descriptor = { + .bLength = sizeof(gser_call_mgmt_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, + .bmCapabilities = 0, + /* .bDataInterface = DYNAMIC */ +}; + +static struct usb_cdc_acm_descriptor gser_descriptor = { + .bLength = sizeof(gser_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + .bmCapabilities = USB_CDC_CAP_LINE, +}; + +static struct usb_cdc_union_desc gser_union_desc = { + .bLength = sizeof(gser_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + /* full speed support: */ +static struct usb_endpoint_descriptor gser_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = 1 << GS_LOG2_NOTIFY_INTERVAL, +}; + static struct usb_endpoint_descriptor gser_fs_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -71,12 +140,25 @@ static struct usb_endpoint_descriptor gser_fs_out_desc = { static struct usb_descriptor_header *gser_fs_function[] = { (struct usb_descriptor_header *) &gser_interface_desc, + (struct usb_descriptor_header *) &gser_header_desc, + (struct usb_descriptor_header *) &gser_call_mgmt_descriptor, + (struct usb_descriptor_header *) &gser_descriptor, + (struct usb_descriptor_header *) &gser_union_desc, + (struct usb_descriptor_header *) &gser_fs_notify_desc, (struct usb_descriptor_header *) &gser_fs_in_desc, (struct usb_descriptor_header *) &gser_fs_out_desc, NULL, }; /* high speed support: */ +static struct usb_endpoint_descriptor gser_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = GS_LOG2_NOTIFY_INTERVAL+4, +}; static struct usb_endpoint_descriptor gser_hs_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, @@ -94,6 +176,11 @@ static struct usb_endpoint_descriptor gser_hs_out_desc = { static struct usb_descriptor_header *gser_hs_function[] = { (struct usb_descriptor_header *) &gser_interface_desc, + (struct usb_descriptor_header *) &gser_header_desc, + (struct usb_descriptor_header *) &gser_call_mgmt_descriptor, + (struct usb_descriptor_header *) &gser_descriptor, + (struct usb_descriptor_header *) &gser_union_desc, + (struct usb_descriptor_header *) &gser_hs_notify_desc, (struct usb_descriptor_header *) &gser_hs_in_desc, (struct usb_descriptor_header *) &gser_hs_out_desc, NULL, @@ -114,12 +201,36 @@ static struct usb_endpoint_descriptor gser_ss_out_desc = { }; static struct usb_ss_ep_comp_descriptor gser_ss_bulk_comp_desc = { - .bLength = sizeof gser_ss_bulk_comp_desc, + .bLength = sizeof(gser_ss_bulk_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, }; +static struct usb_endpoint_descriptor gser_ss_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = GS_LOG2_NOTIFY_INTERVAL+4, +}; + +static struct usb_ss_ep_comp_descriptor gser_ss_notify_comp_desc = { + .bLength = sizeof(gser_ss_notify_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + .wBytesPerInterval = cpu_to_le16(GS_NOTIFY_MAXPACKET), +}; + static struct usb_descriptor_header *gser_ss_function[] = { (struct usb_descriptor_header *) &gser_interface_desc, + (struct usb_descriptor_header *) &gser_header_desc, + (struct usb_descriptor_header *) &gser_call_mgmt_descriptor, + (struct usb_descriptor_header *) &gser_descriptor, + (struct usb_descriptor_header *) &gser_union_desc, + (struct usb_descriptor_header *) &gser_ss_notify_desc, + (struct usb_descriptor_header *) &gser_ss_notify_comp_desc, (struct usb_descriptor_header *) &gser_ss_in_desc, (struct usb_descriptor_header *) &gser_ss_bulk_comp_desc, (struct usb_descriptor_header *) &gser_ss_out_desc, @@ -145,13 +256,131 @@ static struct usb_gadget_strings *gser_strings[] = { }; /*-------------------------------------------------------------------------*/ +static void gser_complete_set_line_coding(struct usb_ep *ep, + struct usb_request *req) +{ + struct f_gser *gser = ep->driver_data; + struct usb_composite_dev *cdev = gser->port.func.config->cdev; + + if (req->status != 0) { + dev_dbg(&cdev->gadget->dev, "gser ttyGS%d completion, err %d\n", + gser->port_num, req->status); + return; + } + + /* normal completion */ + if (req->actual != sizeof(gser->port_line_coding)) { + dev_dbg(&cdev->gadget->dev, "gser ttyGS%d short resp, len %d\n", + gser->port_num, req->actual); + usb_ep_set_halt(ep); + } else { + struct usb_cdc_line_coding *value = req->buf; + + gser->port_line_coding = *value; + } +} + +static int +gser_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_gser *gser = func_to_gser(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + /* SET_LINE_CODING ... just read and save what the host sends */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_LINE_CODING: + if (w_length != sizeof(struct usb_cdc_line_coding)) + goto invalid; + + value = w_length; + cdev->gadget->ep0->driver_data = gser; + req->complete = gser_complete_set_line_coding; + break; + + /* GET_LINE_CODING ... return what host sent, or initial value */ + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_GET_LINE_CODING: + value = min_t(unsigned, w_length, + sizeof(struct usb_cdc_line_coding)); + memcpy(req->buf, &gser->port_line_coding, value); + break; + + /* SET_CONTROL_LINE_STATE ... save what the host sent */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: + + value = 0; + gser->port_handshake_bits = w_value; + pr_debug("%s: USB_CDC_REQ_SET_CONTROL_LINE_STATE: DTR:%d RST:%d\n", + __func__, w_value & ACM_CTRL_DTR ? 1 : 0, + w_value & ACM_CTRL_RTS ? 1 : 0); + + if (gser->port.notify_modem) + gser->port.notify_modem(&gser->port, 0, w_value); + + break; + + default: +invalid: + dev_dbg(&cdev->gadget->dev, + "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + dev_dbg(&cdev->gadget->dev, + "gser ttyGS%d req%02x.%02x v%04x i%04x l%d\n", + gser->port_num, ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "gser response on ttyGS%d, err %d\n", + gser->port_num, value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} static int gser_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct f_gser *gser = func_to_gser(f); struct usb_composite_dev *cdev = f->config->cdev; + int rc = 0; /* we know alt == 0, so this is an activation or a reset */ + if (gser->notify->driver_data) { + dev_dbg(&cdev->gadget->dev, + "reset generic ctl ttyGS%d\n", gser->port_num); + usb_ep_disable(gser->notify); + } + + if (!gser->notify->desc) { + if (config_ep_by_speed(cdev->gadget, f, gser->notify)) { + gser->notify->desc = NULL; + return -EINVAL; + } + } + + rc = usb_ep_enable(gser->notify); + if (rc) { + ERROR(cdev, "can't enable %s, result %d\n", + gser->notify->name, rc); + return rc; + } + gser->notify->driver_data = gser; if (gser->port.in->enabled) { dev_dbg(&cdev->gadget->dev, @@ -169,7 +398,8 @@ static int gser_set_alt(struct usb_function *f, unsigned intf, unsigned alt) } } gserial_connect(&gser->port, gser->port_num); - return 0; + gser->online = 1; + return rc; } static void gser_disable(struct usb_function *f) @@ -180,6 +410,176 @@ static void gser_disable(struct usb_function *f) dev_dbg(&cdev->gadget->dev, "generic ttyGS%d deactivated\n", gser->port_num); gserial_disconnect(&gser->port); + usb_ep_fifo_flush(gser->notify); + usb_ep_disable(gser->notify); + gser->online = 0; +} + +static int gser_notify(struct f_gser *gser, u8 type, u16 value, + void *data, unsigned length) +{ + struct usb_ep *ep = gser->notify; + struct usb_request *req; + struct usb_cdc_notification *notify; + const unsigned len = sizeof(*notify) + length; + void *buf; + int status; + struct usb_composite_dev *cdev = gser->port.func.config->cdev; + + req = gser->notify_req; + gser->notify_req = NULL; + gser->pending = false; + + req->length = len; + notify = req->buf; + buf = notify + 1; + + notify->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + notify->bNotificationType = type; + notify->wValue = cpu_to_le16(value); + notify->wIndex = cpu_to_le16(gser->data_id); + notify->wLength = cpu_to_le16(length); + memcpy(buf, data, length); + + status = usb_ep_queue(ep, req, GFP_ATOMIC); + if (status < 0) { + ERROR(cdev, "gser ttyGS%d can't notify serial state, %d\n", + gser->port_num, status); + gser->notify_req = req; + } + + return status; +} + +static int gser_notify_serial_state(struct f_gser *gser) +{ + int status; + unsigned long flags; + struct usb_composite_dev *cdev = gser->port.func.config->cdev; + + spin_lock_irqsave(&gser->lock, flags); + if (gser->notify_req) { + DBG(cdev, "gser ttyGS%d serial state %04x\n", + gser->port_num, gser->serial_state); + status = gser_notify(gser, USB_CDC_NOTIFY_SERIAL_STATE, + 0, &gser->serial_state, + sizeof(gser->serial_state)); + } else { + gser->pending = true; + status = 0; + } + + spin_unlock_irqrestore(&gser->lock, flags); + return status; +} + +static void gser_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_gser *gser = req->context; + u8 doit = false; + unsigned long flags; + + /* on this call path we do NOT hold the port spinlock, + * which is why ACM needs its own spinlock + */ + + spin_lock_irqsave(&gser->lock, flags); + if (req->status != -ESHUTDOWN) + doit = gser->pending; + + gser->notify_req = req; + spin_unlock_irqrestore(&gser->lock, flags); + + if (doit && gser->online) + gser_notify_serial_state(gser); +} + +static void gser_connect(struct gserial *port) +{ + struct f_gser *gser = port_to_gser(port); + + gser->serial_state |= ACM_CTRL_DSR | ACM_CTRL_DCD; + gser_notify_serial_state(gser); +} + +unsigned int gser_get_dtr(struct gserial *port) +{ + struct f_gser *gser = port_to_gser(port); + + if (gser->port_handshake_bits & ACM_CTRL_DTR) + return 1; + else + return 0; +} + +unsigned int gser_get_rts(struct gserial *port) +{ + struct f_gser *gser = port_to_gser(port); + + if (gser->port_handshake_bits & ACM_CTRL_RTS) + return 1; + else + return 0; +} + +unsigned int gser_send_carrier_detect(struct gserial *port, unsigned int yes) +{ + u16 state; + struct f_gser *gser = port_to_gser(port); + + state = gser->serial_state; + state &= ~ACM_CTRL_DCD; + if (yes) + state |= ACM_CTRL_DCD; + + gser->serial_state = state; + return gser_notify_serial_state(gser); +} + +unsigned int gser_send_ring_indicator(struct gserial *port, unsigned int yes) +{ + u16 state; + struct f_gser *gser = port_to_gser(port); + + state = gser->serial_state; + state &= ~ACM_CTRL_RI; + if (yes) + state |= ACM_CTRL_RI; + + gser->serial_state = state; + return gser_notify_serial_state(gser); +} + +static void gser_disconnect(struct gserial *port) +{ + struct f_gser *gser = port_to_gser(port); + + gser->serial_state &= ~(ACM_CTRL_DSR | ACM_CTRL_DCD); + gser_notify_serial_state(gser); +} + +static int gser_send_break(struct gserial *port, int duration) +{ + u16 state; + struct f_gser *gser = port_to_gser(port); + + state = gser->serial_state; + state &= ~ACM_CTRL_BRK; + if (duration) + state |= ACM_CTRL_BRK; + + gser->serial_state = state; + return gser_notify_serial_state(gser); +} + +static int gser_send_modem_ctrl_bits(struct gserial *port, int ctrl_bits) +{ + struct f_gser *gser = port_to_gser(port); + + gser->serial_state = ctrl_bits; + + return gser_notify_serial_state(gser); } /*-------------------------------------------------------------------------*/ @@ -225,6 +625,21 @@ static int gser_bind(struct usb_configuration *c, struct usb_function *f) goto fail; gser->port.out = ep; + ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_notify_desc); + if (!ep) + goto fail; + gser->notify = ep; + + /* allocate notification */ + gser->notify_req = gs_alloc_req(ep, + sizeof(struct usb_cdc_notification) + 2, + GFP_KERNEL); + if (!gser->notify_req) + goto fail; + + gser->notify_req->complete = gser_notify_complete; + gser->notify_req->context = gser; + /* support all relevant hardware speeds... we expect that when * hardware is dual speed, all bulk-capable endpoints work at * both speeds @@ -235,6 +650,11 @@ static int gser_bind(struct usb_configuration *c, struct usb_function *f) gser_ss_in_desc.bEndpointAddress = gser_fs_in_desc.bEndpointAddress; gser_ss_out_desc.bEndpointAddress = gser_fs_out_desc.bEndpointAddress; + gser_hs_notify_desc.bEndpointAddress = + gser_fs_notify_desc.bEndpointAddress; + gser_ss_notify_desc.bEndpointAddress = + gser_fs_notify_desc.bEndpointAddress; + status = usb_assign_descriptors(f, gser_fs_function, gser_hs_function, gser_ss_function); if (status) @@ -247,6 +667,9 @@ static int gser_bind(struct usb_configuration *c, struct usb_function *f) return 0; fail: + if (gser->notify_req) + gs_free_req(gser->notify, gser->notify_req); + ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); return status; @@ -327,7 +750,10 @@ static void gser_free(struct usb_function *f) static void gser_unbind(struct usb_configuration *c, struct usb_function *f) { + struct f_gser *gser = func_to_gser(f); + usb_free_all_descriptors(f); + gs_free_req(gser->notify, gser->notify_req); } static struct usb_function *gser_alloc(struct usb_function_instance *fi) @@ -342,6 +768,7 @@ static struct usb_function *gser_alloc(struct usb_function_instance *fi) opts = container_of(fi, struct f_serial_opts, func_inst); + spin_lock_init(&gser->lock); gser->port_num = opts->port_num; gser->port.func.name = "gser"; @@ -352,6 +779,24 @@ static struct usb_function *gser_alloc(struct usb_function_instance *fi) gser->port.func.disable = gser_disable; gser->port.func.free_func = gser_free; + /* We support only three ports for now */ + if (opts->port_num == 0) + gser->port.func.name = "modem"; + else if (opts->port_num == 1) + gser->port.func.name = "nmea"; + else + gser->port.func.name = "modem2"; + + gser->port.func.setup = gser_setup; + gser->port.connect = gser_connect; + gser->port.get_dtr = gser_get_dtr; + gser->port.get_rts = gser_get_rts; + gser->port.send_carrier_detect = gser_send_carrier_detect; + gser->port.send_ring_indicator = gser_send_ring_indicator; + gser->port.send_modem_ctrl_bits = gser_send_modem_ctrl_bits; + gser->port.disconnect = gser_disconnect; + gser->port.send_break = gser_send_break; + return &gser->port.func; } diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index f7771d86ad6c..7c1cbb7e44fa 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -4,6 +4,7 @@ * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) * Copyright (C) 2008 David Brownell * Copyright (C) 2008 by Nokia Corporation + * Copyright (c) 2013-2016 The Linux Foundation. All rights reserved. * * This code also borrows from usbserial.c, which is * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com) @@ -608,7 +609,8 @@ static void gs_write_complete(struct usb_ep *ep, struct usb_request *req) /* FALL THROUGH */ case 0: /* normal completion */ - gs_start_tx(port); + if (port->port_usb) + gs_start_tx(port); break; case -ESHUTDOWN: @@ -1007,6 +1009,83 @@ static int gs_break_ctl(struct tty_struct *tty, int duration) return status; } +static int gs_tiocmget(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + struct gserial *gser; + unsigned int result = 0; + + spin_lock_irq(&port->port_lock); + gser = port->port_usb; + if (!gser) { + result = -ENODEV; + goto fail; + } + + if (gser->get_dtr) + result |= (gser->get_dtr(gser) ? TIOCM_DTR : 0); + + if (gser->get_rts) + result |= (gser->get_rts(gser) ? TIOCM_RTS : 0); + + if (gser->serial_state & TIOCM_CD) + result |= TIOCM_CD; + + if (gser->serial_state & TIOCM_RI) + result |= TIOCM_RI; + +fail: + spin_unlock_irq(&port->port_lock); + return result; +} + +static int gs_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct gs_port *port = tty->driver_data; + struct gserial *gser; + int status = 0; + + spin_lock_irq(&port->port_lock); + gser = port->port_usb; + + if (!gser) { + status = -ENODEV; + goto fail; + } + + if (set & TIOCM_RI) { + if (gser->send_ring_indicator) { + gser->serial_state |= TIOCM_RI; + status = gser->send_ring_indicator(gser, 1); + } + } + + if (clear & TIOCM_RI) { + if (gser->send_ring_indicator) { + gser->serial_state &= ~TIOCM_RI; + status = gser->send_ring_indicator(gser, 0); + } + } + + if (set & TIOCM_CD) { + if (gser->send_carrier_detect) { + gser->serial_state |= TIOCM_CD; + status = gser->send_carrier_detect(gser, 1); + } + } + + if (clear & TIOCM_CD) { + if (gser->send_carrier_detect) { + gser->serial_state &= ~TIOCM_CD; + status = gser->send_carrier_detect(gser, 0); + } + } +fail: + spin_unlock_irq(&port->port_lock); + return status; +} + static const struct tty_operations gs_tty_ops = { .open = gs_open, .close = gs_close, @@ -1017,6 +1096,8 @@ static const struct tty_operations gs_tty_ops = { .chars_in_buffer = gs_chars_in_buffer, .unthrottle = gs_unthrottle, .break_ctl = gs_break_ctl, + .tiocmget = gs_tiocmget, + .tiocmset = gs_tiocmset, }; /*-------------------------------------------------------------------------*/ @@ -1296,7 +1377,8 @@ static int userial_init(void) gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; gs_tty_driver->subtype = SERIAL_TYPE_NORMAL; - gs_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + gs_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV + | TTY_DRIVER_RESET_TERMIOS; gs_tty_driver->init_termios = tty_std_termios; /* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on diff --git a/drivers/usb/gadget/function/u_serial.h b/drivers/usb/gadget/function/u_serial.h index c20210c0babd..50c801cd16d2 100644 --- a/drivers/usb/gadget/function/u_serial.h +++ b/drivers/usb/gadget/function/u_serial.h @@ -45,11 +45,21 @@ struct gserial { /* REVISIT avoid this CDC-ACM support harder ... */ struct usb_cdc_line_coding port_line_coding; /* 9600-8-N-1 etc */ + u16 serial_state; + + /* control signal callbacks*/ + unsigned int (*get_dtr)(struct gserial *p); + unsigned int (*get_rts)(struct gserial *p); /* notification callbacks */ void (*connect)(struct gserial *p); void (*disconnect)(struct gserial *p); int (*send_break)(struct gserial *p, int duration); + unsigned int (*send_carrier_detect)(struct gserial *p, unsigned int); + unsigned int (*send_ring_indicator)(struct gserial *p, unsigned int); + int (*send_modem_ctrl_bits)(struct gserial *p, int ctrl_bits); + /* notification changes to modem */ + void (*notify_modem)(void *gser, u8 portno, int ctrl_bits); }; /* utilities to allocate/free request and buffer */ -- cgit v1.2.3