aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/class/cdc-acm.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/class/cdc-acm.c')
-rw-r--r--drivers/usb/class/cdc-acm.c114
1 files changed, 98 insertions, 16 deletions
diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c
index 98199628e39..14de3b1b6a2 100644
--- a/drivers/usb/class/cdc-acm.c
+++ b/drivers/usb/class/cdc-acm.c
@@ -212,7 +212,41 @@ static int acm_write_start(struct acm *acm)
}
return rc;
}
+/*
+ * attributes exported through sysfs
+ */
+static ssize_t show_caps
+(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct acm *acm = usb_get_intfdata(intf);
+
+ return sprintf(buf, "%d", acm->ctrl_caps);
+}
+static DEVICE_ATTR(bmCapabilities, S_IRUGO, show_caps, NULL);
+
+static ssize_t show_country_codes
+(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct acm *acm = usb_get_intfdata(intf);
+
+ memcpy(buf, acm->country_codes, acm->country_code_size);
+ return acm->country_code_size;
+}
+static DEVICE_ATTR(wCountryCodes, S_IRUGO, show_country_codes, NULL);
+
+static ssize_t show_country_rel_date
+(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct acm *acm = usb_get_intfdata(intf);
+
+ return sprintf(buf, "%d", acm->country_rel_date);
+}
+
+static DEVICE_ATTR(iCountryCodeRelDate, S_IRUGO, show_country_rel_date, NULL);
/*
* Interrupt handlers for various ACM device responses
*/
@@ -326,10 +360,16 @@ static void acm_rx_tasklet(unsigned long _acm)
struct tty_struct *tty = acm->tty;
struct acm_ru *rcv;
unsigned long flags;
- int i = 0;
+ unsigned char throttled;
dbg("Entering acm_rx_tasklet");
- if (!ACM_READY(acm) || acm->throttle)
+ if (!ACM_READY(acm))
+ return;
+
+ spin_lock_irqsave(&acm->throttle_lock, flags);
+ throttled = acm->throttle;
+ spin_unlock_irqrestore(&acm->throttle_lock, flags);
+ if (throttled)
return;
next_buffer:
@@ -346,22 +386,20 @@ next_buffer:
dbg("acm_rx_tasklet: procesing buf 0x%p, size = %d", buf, buf->size);
tty_buffer_request_room(tty, buf->size);
- if (!acm->throttle)
+ spin_lock_irqsave(&acm->throttle_lock, flags);
+ throttled = acm->throttle;
+ spin_unlock_irqrestore(&acm->throttle_lock, flags);
+ if (!throttled)
tty_insert_flip_string(tty, buf->base, buf->size);
tty_flip_buffer_push(tty);
- spin_lock(&acm->throttle_lock);
- if (acm->throttle) {
- dbg("Throtteling noticed");
- memmove(buf->base, buf->base + i, buf->size - i);
- buf->size -= i;
- spin_unlock(&acm->throttle_lock);
+ if (throttled) {
+ dbg("Throttling noticed");
spin_lock_irqsave(&acm->read_lock, flags);
list_add(&buf->list, &acm->filled_read_bufs);
spin_unlock_irqrestore(&acm->read_lock, flags);
return;
}
- spin_unlock(&acm->throttle_lock);
spin_lock_irqsave(&acm->read_lock, flags);
list_add(&buf->list, &acm->spare_read_bufs);
@@ -467,7 +505,8 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)
goto bail_out;
}
- if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS))
+ if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS) &&
+ (acm->ctrl_caps & USB_CDC_CAP_LINE))
goto full_bailout;
INIT_LIST_HEAD(&acm->spare_read_urbs);
@@ -480,6 +519,8 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)
list_add(&(acm->rb[i].list), &acm->spare_read_bufs);
}
+ acm->throttle = 0;
+
tasklet_schedule(&acm->urb_task);
done:
@@ -507,6 +548,7 @@ static void acm_tty_unregister(struct acm *acm)
usb_free_urb(acm->writeurb);
for (i = 0; i < nr; i++)
usb_free_urb(acm->ru[i].urb);
+ kfree(acm->country_codes);
kfree(acm);
}
@@ -754,6 +796,7 @@ static int acm_probe (struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_cdc_union_desc *union_header = NULL;
+ struct usb_cdc_country_functional_desc *cfd = NULL;
char *buffer = intf->altsetting->extra;
int buflen = intf->altsetting->extralen;
struct usb_interface *control_interface;
@@ -817,8 +860,9 @@ static int acm_probe (struct usb_interface *intf,
union_header = (struct usb_cdc_union_desc *)
buffer;
break;
- case USB_CDC_COUNTRY_TYPE: /* maybe somehow export */
- break; /* for now we ignore it */
+ case USB_CDC_COUNTRY_TYPE: /* export through sysfs*/
+ cfd = (struct usb_cdc_country_functional_desc *)buffer;
+ break;
case USB_CDC_HEADER_TYPE: /* maybe check version */
break; /* for now we ignore it */
case USB_CDC_ACM_TYPE:
@@ -976,6 +1020,34 @@ skip_normal_probe:
goto alloc_fail7;
}
+ usb_set_intfdata (intf, acm);
+
+ i = device_create_file(&intf->dev, &dev_attr_bmCapabilities);
+ if (i < 0)
+ goto alloc_fail8;
+
+ if (cfd) { /* export the country data */
+ acm->country_codes = kmalloc(cfd->bLength - 4, GFP_KERNEL);
+ if (!acm->country_codes)
+ goto skip_countries;
+ acm->country_code_size = cfd->bLength - 4;
+ memcpy(acm->country_codes, (u8 *)&cfd->wCountyCode0, cfd->bLength - 4);
+ acm->country_rel_date = cfd->iCountryCodeRelDate;
+
+ i = device_create_file(&intf->dev, &dev_attr_wCountryCodes);
+ if (i < 0) {
+ kfree(acm->country_codes);
+ goto skip_countries;
+ }
+
+ i = device_create_file(&intf->dev, &dev_attr_iCountryCodeRelDate);
+ if (i < 0) {
+ kfree(acm->country_codes);
+ goto skip_countries;
+ }
+ }
+
+skip_countries:
usb_fill_int_urb(acm->ctrlurb, usb_dev, usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress),
acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm, epctrl->bInterval);
acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
@@ -999,9 +1071,10 @@ skip_normal_probe:
tty_register_device(acm_tty_driver, minor, &control_interface->dev);
acm_table[minor] = acm;
- usb_set_intfdata (intf, acm);
- return 0;
+ return 0;
+alloc_fail8:
+ usb_free_urb(acm->writeurb);
alloc_fail7:
for (i = 0; i < num_rx_buf; i++)
usb_buffer_free(usb_dev, acm->readsize, acm->rb[i].base, acm->rb[i].dma);
@@ -1020,7 +1093,7 @@ alloc_fail:
static void acm_disconnect(struct usb_interface *intf)
{
- struct acm *acm = usb_get_intfdata (intf);
+ struct acm *acm = usb_get_intfdata(intf);
struct usb_device *usb_dev = interface_to_usbdev(intf);
int i;
@@ -1034,6 +1107,11 @@ static void acm_disconnect(struct usb_interface *intf)
mutex_unlock(&open_mutex);
return;
}
+ if (acm->country_codes){
+ device_remove_file(&intf->dev, &dev_attr_wCountryCodes);
+ device_remove_file(&intf->dev, &dev_attr_iCountryCodeRelDate);
+ }
+ device_remove_file(&intf->dev, &dev_attr_bmCapabilities);
acm->dev = NULL;
usb_set_intfdata(acm->control, NULL);
usb_set_intfdata(acm->data, NULL);
@@ -1092,6 +1170,10 @@ static struct usb_device_id acm_ids[] = {
{ USB_DEVICE(0x0ace, 0x1611), /* ZyDAS 56K USB MODEM - new version */
.driver_info = SINGLE_RX_URB, /* firmware bug */
},
+ { USB_DEVICE(0x22b8, 0x7000), /* Motorola Q Phone */
+ .driver_info = NO_UNION_NORMAL, /* has no union descriptor */
+ },
+
/* control interfaces with various AT-command sets */
{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
USB_CDC_ACM_PROTO_AT_V25TER) },