struct wdm_device {
u8 *inbuf; /* buffer for response */
u8 *outbuf; /* buffer for command */
u8 *sbuf; /* buffer for status */
u8 *ubuf; /* buffer for copy to user space */
/* return intfdata if we own the interface, else look up intf in the list */ staticstruct wdm_device *wdm_find_device(struct usb_interface *intf)
{ struct wdm_device *desc;
if (status) { switch (status) { case -ENOENT:
dev_dbg(&desc->intf->dev, "nonzero urb status received: -ENOENT\n"); goto skip_error; case -ECONNRESET:
dev_dbg(&desc->intf->dev, "nonzero urb status received: -ECONNRESET\n"); goto skip_error; case -ESHUTDOWN:
dev_dbg(&desc->intf->dev, "nonzero urb status received: -ESHUTDOWN\n"); goto skip_error; case -EPIPE:
dev_err(&desc->intf->dev, "nonzero urb status received: -EPIPE\n"); break; default:
dev_err(&desc->intf->dev, "Unexpected error %d\n", status); break;
}
}
if (test_bit(WDM_WWAN_IN_USE, &desc->flags)) {
wdm_wwan_rx(desc, length); goto out;
}
/* * only set a new error if there is no previous error. * Errors are only cleared during read/open * Avoid propagating -EPIPE (stall) to userspace since it is * better handled as an empty read
*/ if (desc->rerr == 0 && status != -EPIPE)
desc->rerr = status;
if (length + desc->length > desc->wMaxCommand) { /* The buffer would overflow */
set_bit(WDM_OVERFLOW, &desc->flags);
} else { /* we may already be in overflow */ if (!test_bit(WDM_OVERFLOW, &desc->flags)) {
memmove(desc->ubuf + desc->length, desc->inbuf, length);
desc->length += length;
}
}
skip_error:
if (desc->rerr) { /* * If there was a ZLP or an error, userspace may decide to not * read any data after poll'ing. * We should respond to further attempts from the device to send * data, so that we can get unstuck.
*/
skip_zlp:
schedule_work(&desc->service_outs_intr);
} else {
set_bit(WDM_READ, &desc->flags);
wake_up(&desc->wait);
}
out:
spin_unlock_irqrestore(&desc->iuspin, flags);
}
staticvoid wdm_int_callback(struct urb *urb)
{ unsignedlong flags; int rv = 0; int responding; int status = urb->status; struct wdm_device *desc; struct usb_cdc_notification *dr;
desc = urb->context;
dr = (struct usb_cdc_notification *)desc->sbuf;
if (status) { switch (status) { case -ESHUTDOWN: case -ENOENT: case -ECONNRESET: return; /* unplug */ case -EPIPE:
set_bit(WDM_INT_STALL, &desc->flags);
dev_err(&desc->intf->dev, "Stall on int endpoint\n"); goto sw; /* halt is cleared in work */ default:
dev_err_ratelimited(&desc->intf->dev, "nonzero urb status received: %d\n", status); break;
}
}
switch (dr->bNotificationType) { case USB_CDC_NOTIFY_RESPONSE_AVAILABLE:
dev_dbg(&desc->intf->dev, "NOTIFY_RESPONSE_AVAILABLE received: index %d len %d\n",
le16_to_cpu(dr->wIndex), le16_to_cpu(dr->wLength)); break;
case USB_CDC_NOTIFY_NETWORK_CONNECTION:
dev_dbg(&desc->intf->dev, "NOTIFY_NETWORK_CONNECTION %s network\n",
dr->wValue ? "connected to" : "disconnected from"); gotoexit; case USB_CDC_NOTIFY_SPEED_CHANGE:
dev_dbg(&desc->intf->dev, "SPEED_CHANGE received (len %u)\n",
urb->actual_length); gotoexit; default:
clear_bit(WDM_POLL_RUNNING, &desc->flags);
dev_err(&desc->intf->dev, "unknown notification %d received: index %d len %d\n",
dr->bNotificationType,
le16_to_cpu(dr->wIndex),
le16_to_cpu(dr->wLength)); gotoexit;
}
spin_lock_irqsave(&desc->iuspin, flags);
responding = test_and_set_bit(WDM_RESPONDING, &desc->flags); if (!desc->resp_count++ && !responding
&& !test_bit(WDM_DISCONNECTING, &desc->flags)
&& !test_bit(WDM_SUSPENDING, &desc->flags)) {
rv = usb_submit_urb(desc->response, GFP_ATOMIC);
dev_dbg(&desc->intf->dev, "submit response URB %d\n", rv);
}
spin_unlock_irqrestore(&desc->iuspin, flags); if (rv < 0) {
clear_bit(WDM_RESPONDING, &desc->flags); if (rv == -EPERM) return; if (rv == -ENOMEM) {
sw:
rv = schedule_work(&desc->rxwork); if (rv)
dev_err(&desc->intf->dev, "Cannot schedule work\n");
}
} exit:
rv = usb_submit_urb(urb, GFP_ATOMIC); if (rv)
dev_err(&desc->intf->dev, "%s - usb_submit_urb failed with result %d\n",
__func__, rv);
}
staticvoid poison_urbs(struct wdm_device *desc)
{ /* the order here is essential */
usb_poison_urb(desc->command);
usb_poison_urb(desc->validity);
usb_poison_urb(desc->response);
}
staticvoid unpoison_urbs(struct wdm_device *desc)
{ /* * the order here is not essential * it is symmetrical just to be nice
*/
usb_unpoison_urb(desc->response);
usb_unpoison_urb(desc->validity);
usb_unpoison_urb(desc->command);
}
if (count > desc->wMaxCommand)
count = desc->wMaxCommand;
spin_lock_irq(&desc->iuspin);
we = desc->werr;
desc->werr = 0;
spin_unlock_irq(&desc->iuspin); if (we < 0) return usb_translate_errors(we);
buf = memdup_user(buffer, count); if (IS_ERR(buf)) return PTR_ERR(buf);
/* concurrent writes and disconnect */
r = mutex_lock_interruptible(&desc->wlock);
rv = -ERESTARTSYS; if (r) goto out_free_mem;
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
rv = -ENODEV; goto out_free_mem_lock;
}
r = usb_autopm_get_interface(desc->intf); if (r < 0) {
rv = usb_translate_errors(r); goto out_free_mem_lock;
}
if (!(file->f_flags & O_NONBLOCK))
r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE,
&desc->flags)); else if (test_bit(WDM_IN_USE, &desc->flags))
r = -EAGAIN;
if (test_bit(WDM_RESETTING, &desc->flags))
r = -EIO;
if (test_bit(WDM_DISCONNECTING, &desc->flags))
r = -ENODEV;
/* * Submit the read urb if resp_count is non-zero. * * Called with desc->iuspin locked
*/ staticint service_outstanding_interrupt(struct wdm_device *desc)
{ int rv = 0;
/* submit read urb only if the device is waiting for it */ if (!desc->resp_count || !--desc->resp_count) goto out;
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
rv = -ENODEV; goto out;
} if (test_bit(WDM_RESETTING, &desc->flags)) {
rv = -EIO; goto out;
}
set_bit(WDM_RESPONDING, &desc->flags);
spin_unlock_irq(&desc->iuspin);
rv = usb_submit_urb(desc->response, GFP_KERNEL);
spin_lock_irq(&desc->iuspin); if (rv) { if (!test_bit(WDM_DISCONNECTING, &desc->flags))
dev_err(&desc->intf->dev, "usb_submit_urb failed with result %d\n", rv);
/* make sure the next notification trigger a submit */
clear_bit(WDM_RESPONDING, &desc->flags);
desc->resp_count = 0;
}
out: return rv;
}
static ssize_t wdm_read
(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{ int rv, cntr; int i = 0; struct wdm_device *desc = file->private_data;
for (i = 0; i < desc->length - cntr; i++)
desc->ubuf[i] = desc->ubuf[i + cntr];
desc->length -= cntr; /* in case we had outstanding data */ if (!desc->length) {
clear_bit(WDM_READ, &desc->flags);
service_outstanding_interrupt(desc);
}
spin_unlock_irq(&desc->iuspin);
rv = cntr;
err:
mutex_unlock(&desc->rlock); return rv;
}
staticint wdm_wait_for_response(struct file *file, long timeout)
{ struct wdm_device *desc = file->private_data; long rv; /* Use long here because (int) MAX_SCHEDULE_TIMEOUT < 0. */
/* * Needs both flags. We cannot do with one because resetting it would * cause a race with write() yet we need to signal a disconnect.
*/
rv = wait_event_interruptible_timeout(desc->wait,
!test_bit(WDM_IN_USE, &desc->flags) ||
test_bit(WDM_DISCONNECTING, &desc->flags),
timeout);
/* * To report the correct error. This is best effort. * We are inevitably racing with the hardware.
*/ if (test_bit(WDM_DISCONNECTING, &desc->flags)) return -ENODEV; if (!rv) return -EIO; if (rv < 0) return -EINTR;
/* * You need to send a signal when you react to malicious or defective hardware. * Also, don't abort when fsync() returned -EINVAL, for older kernels which do * not implement wdm_flush() will return -EINVAL.
*/ staticint wdm_fsync(struct file *file, loff_t start, loff_t end, int datasync)
{ return wdm_wait_for_response(file, MAX_SCHEDULE_TIMEOUT);
}
/* * Same with wdm_fsync(), except it uses finite timeout in order to react to * malicious or defective hardware which ceased communication after close() was * implicitly called due to process termination.
*/ staticint wdm_flush(struct file *file, fl_owner_t id)
{ return wdm_wait_for_response(file, WDM_FLUSH_TIMEOUT);
}
/* The interface is both exposed via the WWAN framework and as a * legacy usbmisc chardev. If chardev is already open, just fail * to prevent concurrent usage. Otherwise, switch to WWAN mode.
*/
mutex_lock(&wdm_mutex); if (desc->count) {
mutex_unlock(&wdm_mutex); return -EBUSY;
}
set_bit(WDM_WWAN_IN_USE, &desc->flags);
mutex_unlock(&wdm_mutex);
desc->manage_power(desc->intf, 1);
/* tx is allowed */
wwan_port_txon(port);
/* Start getting events */
rv = usb_submit_urb(desc->validity, GFP_KERNEL); if (rv < 0) {
wwan_port_txoff(port);
desc->manage_power(desc->intf, 0); /* this must be last lest we race with chardev open */
clear_bit(WDM_WWAN_IN_USE, &desc->flags);
}
/* Stop all transfers and disable WWAN mode */
poison_urbs(desc);
desc->manage_power(desc->intf, 0);
clear_bit(WDM_READ, &desc->flags);
unpoison_urbs(desc);
smp_wmb(); /* ordered against wdm_open() */ /* this must be last lest we open a poisoned device */
clear_bit(WDM_WWAN_IN_USE, &desc->flags);
}
rv = usb_submit_urb(desc->command, GFP_KERNEL); if (rv)
usb_autopm_put_interface(intf); else/* One transfer at a time, stop TX until URB completion */
wwan_port_txoff(port);
/* Only register to WWAN core if protocol/type is known */ if (desc->wwanp_type == WWAN_PORT_UNKNOWN) {
dev_info(&intf->dev, "Unknown control protocol\n"); return;
}
port = wwan_create_port(&intf->dev, desc->wwanp_type, &wdm_wwan_port_ops,
NULL, desc); if (IS_ERR(port)) {
dev_err(&intf->dev, "%s: Unable to create WWAN port\n",
dev_name(intf->usb_dev)); return;
}
desc->wwanp = port;
}
staticvoid wdm_wwan_deinit(struct wdm_device *desc)
{ if (!desc->wwanp) return;
/* inbuf has been copied, it is safe to check for outstanding data */
schedule_work(&desc->service_outs_intr);
} #else/* CONFIG_WWAN */ staticvoid wdm_wwan_init(struct wdm_device *desc) {} staticvoid wdm_wwan_deinit(struct wdm_device *desc) {} staticvoid wdm_wwan_rx(struct wdm_device *desc, int length) {} #endif/* CONFIG_WWAN */
staticint wdm_manage_power(struct usb_interface *intf, int on)
{ /* need autopm_get/put here to ensure the usbcore sees the new value */ int rv = usb_autopm_get_interface(intf);
intf->needs_remote_wakeup = on; if (!rv)
usb_autopm_put_interface(intf); return 0;
}
/** * usb_cdc_wdm_register - register a WDM subdriver * @intf: usb interface the subdriver will associate with * @ep: interrupt endpoint to monitor for notifications * @bufsize: maximum message size to support for read/write * @type: Type/protocol of the transported data (MBIM, QMI...) * @manage_power: call-back invoked during open and release to * manage the device's power * Create WDM usb class character device and associate it with intf * without binding, allowing another driver to manage the interface. * * The subdriver will manage the given interrupt endpoint exclusively * and will issue control requests referring to the given intf. It * will otherwise avoid interferring, and in particular not do * usb_set_intfdata/usb_get_intfdata on intf. * * The return value is a pointer to the subdriver's struct usb_driver. * The registering driver is responsible for calling this subdriver's * disconnect, suspend, resume, pre_reset and post_reset methods from * its own.
*/ struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf, struct usb_endpoint_descriptor *ep, int bufsize, enum wwan_port_type type, int (*manage_power)(struct usb_interface *, int))
{ int rv;
/* the spinlock makes sure no new urbs are generated in the callbacks */
spin_lock_irqsave(&desc->iuspin, flags);
set_bit(WDM_DISCONNECTING, &desc->flags);
set_bit(WDM_READ, &desc->flags);
spin_unlock_irqrestore(&desc->iuspin, flags);
wake_up_all(&desc->wait);
mutex_lock(&desc->rlock);
mutex_lock(&desc->wlock);
poison_urbs(desc);
cancel_work_sync(&desc->rxwork);
cancel_work_sync(&desc->service_outs_intr);
mutex_unlock(&desc->wlock);
mutex_unlock(&desc->rlock);
/* the desc->intf pointer used as list key is now invalid */
spin_lock(&wdm_device_list_lock);
list_del(&desc->device_list);
spin_unlock(&wdm_device_list_lock);
if (!desc->count)
cleanup(desc); else
dev_dbg(&intf->dev, "%d open files - postponing cleanup\n", desc->count);
mutex_unlock(&wdm_mutex);
}
/* if this is an autosuspend the caller does the locking */ if (!PMSG_IS_AUTO(message)) {
mutex_lock(&desc->rlock);
mutex_lock(&desc->wlock);
}
spin_lock_irq(&desc->iuspin);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.