2017-11-03 03:28:30 -07:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2008-05-13 08:01:25 -07:00
|
|
|
/*
|
|
|
|
* cdc-wdm.c
|
|
|
|
*
|
|
|
|
* This driver supports USB CDC WCM Device Management.
|
|
|
|
*
|
2009-04-20 08:24:49 -07:00
|
|
|
* Copyright (c) 2007-2009 Oliver Neukum
|
2008-05-13 08:01:25 -07:00
|
|
|
*
|
|
|
|
* Some code taken from cdc-acm.c
|
|
|
|
*
|
|
|
|
* Released under the GPLv2.
|
|
|
|
*
|
|
|
|
* Many thanks to Carl Nordbeck
|
|
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/errno.h>
|
USB: cdc-wdm: implement IOCTL_WDM_MAX_COMMAND
Userspace applications need to know the maximum supported message
size.
The cdc-wdm driver translates between a character device stream
and a message based protocol. Each message is transported as a
usb control message with no further encapsulation or syncronization.
Each read or write on the character device should translate to
exactly one usb control message to ensure that message boundaries
are kept intact. That means that the userspace application must
know the maximum message size supported by the device and driver,
making this size a vital part of the cdc-wdm character device API.
CDC WDM and CDC MBIM functions export the maximum supported
message size through CDC functional descriptors. The cdc-wdm and
cdc_mbim drivers will parse these descriptors and use the value
chosen by the device. The only current way for a userspace
application to retrive the value is by duplicating the descriptor
parsing. This is an unnecessary complex task, and application
writers are likely to postpone it, using a fixed value and adding
a "todo" item.
QMI functions have no way to tell the host what message size they
support. The qmi_wwan driver use a fixed value based on protocol
recommendations and observed device behaviour. Userspace
applications must know and hard code the same value. This scheme
will break if we ever encounter a QMI device needing a device
specific message size quirk. We are currently unable to support
such a device because using a non default size would break the
implicit userspace API.
The message size is currently a hidden attribute of the cdc-wdm
userspace API. Retrieving it is unnecessarily complex, increasing
the possibility of drivers and applications using different limits.
The resulting errors are hard to debug, and can only be replicated
on identical hardware.
Exporting the maximum message size from the driver simplifies the
task for the userspace application, and creates a unified
information source independent of device and function class. It also
serves to document that the message size is part of the cdc-wdm
userspace API.
This proposed API extension has been presented for the authors of
userspace applications and libraries using the current API: libmbim,
libqmi, uqmi, oFono and ModemManager. The replies were:
Aleksander Morgado:
"We do really need max message size for MBIM; and as you say, it may be
good to have the max message size info also for QMI, so the new ioctl
seems a good addition. So +1 from my side, for what it's worth."
Dan Williams:
"Yeah, +1 here. I'd prefer the sysfs file, but the fact that that
doesn't work for fd passing pretty much kills it."
No negative replies are so far received.
Cc: Aleksander Morgado <aleksander@lanedo.com>
Cc: Dan Williams <dcbw@redhat.com>
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Acked-by: Oliver Neukum <oliver@neukum.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2013-03-17 13:00:06 -07:00
|
|
|
#include <linux/ioctl.h>
|
2008-05-13 08:01:25 -07:00
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <linux/bitops.h>
|
|
|
|
#include <linux/poll.h>
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
#include <linux/skbuff.h>
|
2008-05-13 08:01:25 -07:00
|
|
|
#include <linux/usb.h>
|
|
|
|
#include <linux/usb/cdc.h>
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
#include <linux/wwan.h>
|
2008-05-13 08:01:25 -07:00
|
|
|
#include <asm/byteorder.h>
|
2024-10-01 12:35:57 -07:00
|
|
|
#include <linux/unaligned.h>
|
2012-03-06 09:29:22 -07:00
|
|
|
#include <linux/usb/cdc-wdm.h>
|
2008-05-13 08:01:25 -07:00
|
|
|
|
|
|
|
#define DRIVER_AUTHOR "Oliver Neukum"
|
2008-06-19 05:20:18 -07:00
|
|
|
#define DRIVER_DESC "USB Abstract Control Model driver for USB WCM Device Management"
|
2008-05-13 08:01:25 -07:00
|
|
|
|
2010-01-10 07:33:45 -07:00
|
|
|
static const struct usb_device_id wdm_ids[] = {
|
2008-05-13 08:01:25 -07:00
|
|
|
{
|
|
|
|
.match_flags = USB_DEVICE_ID_MATCH_INT_CLASS |
|
|
|
|
USB_DEVICE_ID_MATCH_INT_SUBCLASS,
|
|
|
|
.bInterfaceClass = USB_CLASS_COMM,
|
|
|
|
.bInterfaceSubClass = USB_CDC_SUBCLASS_DMM
|
|
|
|
},
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
2008-10-13 05:05:20 -07:00
|
|
|
MODULE_DEVICE_TABLE (usb, wdm_ids);
|
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
#define WDM_MINOR_BASE 176
|
|
|
|
|
|
|
|
|
|
|
|
#define WDM_IN_USE 1
|
|
|
|
#define WDM_DISCONNECTING 2
|
|
|
|
#define WDM_RESULT 3
|
|
|
|
#define WDM_READ 4
|
|
|
|
#define WDM_INT_STALL 5
|
|
|
|
#define WDM_POLL_RUNNING 6
|
2010-02-27 12:54:59 -07:00
|
|
|
#define WDM_RESPONDING 7
|
2010-02-27 12:55:52 -07:00
|
|
|
#define WDM_SUSPENDING 8
|
2012-02-10 01:44:08 -07:00
|
|
|
#define WDM_RESETTING 9
|
2013-03-12 06:52:42 -07:00
|
|
|
#define WDM_OVERFLOW 10
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
#define WDM_WWAN_IN_USE 11
|
2008-05-13 08:01:25 -07:00
|
|
|
|
|
|
|
#define WDM_MAX 16
|
|
|
|
|
USB: cdc-wdm: Make wdm_flush() interruptible and add wdm_fsync().
syzbot is reporting hung task at wdm_flush() [1], for there is a circular
dependency that wdm_flush() from flip_close() for /dev/cdc-wdm0 forever
waits for /dev/raw-gadget to be closed while close() for /dev/raw-gadget
cannot be called unless close() for /dev/cdc-wdm0 completes.
Tetsuo Handa considered that such circular dependency is an usage error [2]
which corresponds to an unresponding broken hardware [3]. But Alan Stern
responded that we should be prepared for such hardware [4]. Therefore,
this patch changes wdm_flush() to use wait_event_interruptible_timeout()
which gives up after 30 seconds, for hardware that remains silent must be
ignored. The 30 seconds are coming out of thin air.
Changing wait_event() to wait_event_interruptible_timeout() makes error
reporting from close() syscall less reliable. To compensate it, this patch
also implements wdm_fsync() which does not use timeout. Those who want to
be very sure that data has gone out to the device are now advised to call
fsync(), with a caveat that fsync() can return -EINVAL when running on
older kernels which do not implement wdm_fsync().
This patch also fixes three more problems (listed below) found during
exhaustive discussion and testing.
Since multiple threads can concurrently call wdm_write()/wdm_flush(),
we need to use wake_up_all() whenever clearing WDM_IN_USE in order to
make sure that all waiters are woken up. Also, error reporting needs
to use fetch-and-clear approach in order not to report same error for
multiple times.
Since wdm_flush() checks WDM_DISCONNECTING, wdm_write() should as well
check WDM_DISCONNECTING.
In wdm_flush(), since locks are not held, it is not safe to dereference
desc->intf after checking that WDM_DISCONNECTING is not set [5]. Thus,
remove dev_err() from wdm_flush().
[1] https://syzkaller.appspot.com/bug?id=e7b761593b23eb50855b9ea31e3be5472b711186
[2] https://lkml.kernel.org/r/27b7545e-8f41-10b8-7c02-e35a08eb1611@i-love.sakura.ne.jp
[3] https://lkml.kernel.org/r/79ba410f-e0ef-2465-b94f-6b9a4a82adf5@i-love.sakura.ne.jp
[4] https://lkml.kernel.org/r/20200530011040.GB12419@rowland.harvard.edu
[5] https://lkml.kernel.org/r/c85331fc-874c-6e46-a77f-0ef1dc075308@i-love.sakura.ne.jp
Reported-by: syzbot <syzbot+854768b99f19e89d7f81@syzkaller.appspotmail.com>
Cc: stable <stable@vger.kernel.org>
Co-developed-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Oliver Neukum <oneukum@suse.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Link: https://lore.kernel.org/r/20200928141755.3476-1-penguin-kernel@I-love.SAKURA.ne.jp
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-09-28 07:17:55 -07:00
|
|
|
/* we cannot wait forever at flush() */
|
|
|
|
#define WDM_FLUSH_TIMEOUT (30 * HZ)
|
|
|
|
|
2012-01-19 17:49:57 -07:00
|
|
|
/* CDC-WMC r1.1 requires wMaxCommand to be "at least 256 decimal (0x100)" */
|
|
|
|
#define WDM_DEFAULT_BUFSIZE 256
|
2008-05-13 08:01:25 -07:00
|
|
|
|
|
|
|
static DEFINE_MUTEX(wdm_mutex);
|
2012-03-06 09:29:21 -07:00
|
|
|
static DEFINE_SPINLOCK(wdm_device_list_lock);
|
|
|
|
static LIST_HEAD(wdm_device_list);
|
2008-05-13 08:01:25 -07:00
|
|
|
|
|
|
|
/* --- method tables --- */
|
|
|
|
|
|
|
|
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 */
|
|
|
|
|
|
|
|
struct urb *command;
|
|
|
|
struct urb *response;
|
|
|
|
struct urb *validity;
|
|
|
|
struct usb_interface *intf;
|
|
|
|
struct usb_ctrlrequest *orq;
|
|
|
|
struct usb_ctrlrequest *irq;
|
|
|
|
spinlock_t iuspin;
|
|
|
|
|
|
|
|
unsigned long flags;
|
|
|
|
u16 bufsize;
|
|
|
|
u16 wMaxCommand;
|
|
|
|
u16 wMaxPacketSize;
|
|
|
|
__le16 inum;
|
|
|
|
int reslength;
|
|
|
|
int length;
|
|
|
|
int read;
|
|
|
|
int count;
|
|
|
|
dma_addr_t shandle;
|
|
|
|
dma_addr_t ihandle;
|
2012-01-16 04:41:48 -07:00
|
|
|
struct mutex wlock;
|
|
|
|
struct mutex rlock;
|
2008-05-13 08:01:25 -07:00
|
|
|
wait_queue_head_t wait;
|
|
|
|
struct work_struct rxwork;
|
2018-06-14 09:36:46 -07:00
|
|
|
struct work_struct service_outs_intr;
|
2008-05-13 08:01:25 -07:00
|
|
|
int werr;
|
|
|
|
int rerr;
|
2013-10-29 10:29:10 -07:00
|
|
|
int resp_count;
|
2012-03-06 09:29:21 -07:00
|
|
|
|
|
|
|
struct list_head device_list;
|
2012-03-06 09:29:22 -07:00
|
|
|
int (*manage_power)(struct usb_interface *, int);
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
|
|
|
|
enum wwan_port_type wwanp_type;
|
|
|
|
struct wwan_port *wwanp;
|
2008-05-13 08:01:25 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct usb_driver wdm_driver;
|
|
|
|
|
2012-03-06 09:29:21 -07:00
|
|
|
/* return intfdata if we own the interface, else look up intf in the list */
|
|
|
|
static struct wdm_device *wdm_find_device(struct usb_interface *intf)
|
|
|
|
{
|
2012-09-10 13:17:34 -07:00
|
|
|
struct wdm_device *desc;
|
2012-03-06 09:29:21 -07:00
|
|
|
|
|
|
|
spin_lock(&wdm_device_list_lock);
|
|
|
|
list_for_each_entry(desc, &wdm_device_list, device_list)
|
|
|
|
if (desc->intf == intf)
|
2012-09-10 13:17:34 -07:00
|
|
|
goto found;
|
|
|
|
desc = NULL;
|
|
|
|
found:
|
2012-03-06 09:29:21 -07:00
|
|
|
spin_unlock(&wdm_device_list_lock);
|
|
|
|
|
|
|
|
return desc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct wdm_device *wdm_find_device_by_minor(int minor)
|
|
|
|
{
|
2012-09-10 13:17:34 -07:00
|
|
|
struct wdm_device *desc;
|
2012-03-06 09:29:21 -07:00
|
|
|
|
|
|
|
spin_lock(&wdm_device_list_lock);
|
|
|
|
list_for_each_entry(desc, &wdm_device_list, device_list)
|
|
|
|
if (desc->intf->minor == minor)
|
2012-09-10 13:17:34 -07:00
|
|
|
goto found;
|
|
|
|
desc = NULL;
|
|
|
|
found:
|
2012-03-06 09:29:21 -07:00
|
|
|
spin_unlock(&wdm_device_list_lock);
|
|
|
|
|
|
|
|
return desc;
|
|
|
|
}
|
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
/* --- callbacks --- */
|
|
|
|
static void wdm_out_callback(struct urb *urb)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc;
|
2018-06-24 15:08:35 -07:00
|
|
|
unsigned long flags;
|
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
desc = urb->context;
|
2018-06-24 15:08:35 -07:00
|
|
|
spin_lock_irqsave(&desc->iuspin, flags);
|
2008-05-13 08:01:25 -07:00
|
|
|
desc->werr = urb->status;
|
2018-06-24 15:08:35 -07:00
|
|
|
spin_unlock_irqrestore(&desc->iuspin, flags);
|
2008-05-13 08:01:25 -07:00
|
|
|
kfree(desc->outbuf);
|
2012-04-26 12:59:10 -07:00
|
|
|
desc->outbuf = NULL;
|
|
|
|
clear_bit(WDM_IN_USE, &desc->flags);
|
USB: cdc-wdm: Make wdm_flush() interruptible and add wdm_fsync().
syzbot is reporting hung task at wdm_flush() [1], for there is a circular
dependency that wdm_flush() from flip_close() for /dev/cdc-wdm0 forever
waits for /dev/raw-gadget to be closed while close() for /dev/raw-gadget
cannot be called unless close() for /dev/cdc-wdm0 completes.
Tetsuo Handa considered that such circular dependency is an usage error [2]
which corresponds to an unresponding broken hardware [3]. But Alan Stern
responded that we should be prepared for such hardware [4]. Therefore,
this patch changes wdm_flush() to use wait_event_interruptible_timeout()
which gives up after 30 seconds, for hardware that remains silent must be
ignored. The 30 seconds are coming out of thin air.
Changing wait_event() to wait_event_interruptible_timeout() makes error
reporting from close() syscall less reliable. To compensate it, this patch
also implements wdm_fsync() which does not use timeout. Those who want to
be very sure that data has gone out to the device are now advised to call
fsync(), with a caveat that fsync() can return -EINVAL when running on
older kernels which do not implement wdm_fsync().
This patch also fixes three more problems (listed below) found during
exhaustive discussion and testing.
Since multiple threads can concurrently call wdm_write()/wdm_flush(),
we need to use wake_up_all() whenever clearing WDM_IN_USE in order to
make sure that all waiters are woken up. Also, error reporting needs
to use fetch-and-clear approach in order not to report same error for
multiple times.
Since wdm_flush() checks WDM_DISCONNECTING, wdm_write() should as well
check WDM_DISCONNECTING.
In wdm_flush(), since locks are not held, it is not safe to dereference
desc->intf after checking that WDM_DISCONNECTING is not set [5]. Thus,
remove dev_err() from wdm_flush().
[1] https://syzkaller.appspot.com/bug?id=e7b761593b23eb50855b9ea31e3be5472b711186
[2] https://lkml.kernel.org/r/27b7545e-8f41-10b8-7c02-e35a08eb1611@i-love.sakura.ne.jp
[3] https://lkml.kernel.org/r/79ba410f-e0ef-2465-b94f-6b9a4a82adf5@i-love.sakura.ne.jp
[4] https://lkml.kernel.org/r/20200530011040.GB12419@rowland.harvard.edu
[5] https://lkml.kernel.org/r/c85331fc-874c-6e46-a77f-0ef1dc075308@i-love.sakura.ne.jp
Reported-by: syzbot <syzbot+854768b99f19e89d7f81@syzkaller.appspotmail.com>
Cc: stable <stable@vger.kernel.org>
Co-developed-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Oliver Neukum <oneukum@suse.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Link: https://lore.kernel.org/r/20200928141755.3476-1-penguin-kernel@I-love.SAKURA.ne.jp
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-09-28 07:17:55 -07:00
|
|
|
wake_up_all(&desc->wait);
|
2008-05-13 08:01:25 -07:00
|
|
|
}
|
|
|
|
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
static void wdm_wwan_rx(struct wdm_device *desc, int length);
|
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
static void wdm_in_callback(struct urb *urb)
|
|
|
|
{
|
2018-06-24 15:08:35 -07:00
|
|
|
unsigned long flags;
|
2008-05-13 08:01:25 -07:00
|
|
|
struct wdm_device *desc = urb->context;
|
|
|
|
int status = urb->status;
|
2013-03-12 06:52:42 -07:00
|
|
|
int length = urb->actual_length;
|
2008-05-13 08:01:25 -07:00
|
|
|
|
2018-06-24 15:08:35 -07:00
|
|
|
spin_lock_irqsave(&desc->iuspin, flags);
|
2010-02-27 12:54:59 -07:00
|
|
|
clear_bit(WDM_RESPONDING, &desc->flags);
|
2008-05-13 08:01:25 -07:00
|
|
|
|
|
|
|
if (status) {
|
|
|
|
switch (status) {
|
|
|
|
case -ENOENT:
|
|
|
|
dev_dbg(&desc->intf->dev,
|
2016-08-16 06:12:22 -07:00
|
|
|
"nonzero urb status received: -ENOENT\n");
|
2010-02-27 12:54:59 -07:00
|
|
|
goto skip_error;
|
2008-05-13 08:01:25 -07:00
|
|
|
case -ECONNRESET:
|
|
|
|
dev_dbg(&desc->intf->dev,
|
2016-08-16 06:12:22 -07:00
|
|
|
"nonzero urb status received: -ECONNRESET\n");
|
2010-02-27 12:54:59 -07:00
|
|
|
goto skip_error;
|
2008-05-13 08:01:25 -07:00
|
|
|
case -ESHUTDOWN:
|
|
|
|
dev_dbg(&desc->intf->dev,
|
2016-08-16 06:12:22 -07:00
|
|
|
"nonzero urb status received: -ESHUTDOWN\n");
|
2010-02-27 12:54:59 -07:00
|
|
|
goto skip_error;
|
2008-05-13 08:01:25 -07:00
|
|
|
case -EPIPE:
|
2017-04-21 01:01:29 -07:00
|
|
|
dev_err(&desc->intf->dev,
|
2008-08-14 09:37:34 -07:00
|
|
|
"nonzero urb status received: -EPIPE\n");
|
2008-05-13 08:01:25 -07:00
|
|
|
break;
|
|
|
|
default:
|
2008-08-14 09:37:34 -07:00
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"Unexpected error %d\n", status);
|
2008-05-13 08:01:25 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
if (test_bit(WDM_WWAN_IN_USE, &desc->flags)) {
|
|
|
|
wdm_wwan_rx(desc, length);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
cdc-wdm: Clear read pipeline in case of error
Implemented queued response handling. This queue is processed every time the
WDM_READ flag is cleared.
In case of a read error, userspace may not actually read the data, since the
driver returns an error through wdm_poll. After this, the underlying device may
attempt to send us more data, but the queue is not processed. While userspace is
also blocked, because the read error is never cleared.
After this patch, we proactively process the queue on a read error. If there was
an outstanding response to handle, that will clear the error (or go through the
same logic again, if another read error occurs). If there was no outstanding
response, this will bring the queue size back to 0, unblocking a future response
from the underlying device.
Signed-off-by: Robert Foss <robert.foss@collabora.com>
Tested-by: Robert Foss <robert.foss@collabora.com>
Acked-by: Oliver Neukum <oneukum@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-08-09 07:54:52 -07:00
|
|
|
/*
|
|
|
|
* only set a new error if there is no previous error.
|
|
|
|
* Errors are only cleared during read/open
|
2017-09-22 13:18:18 -07:00
|
|
|
* Avoid propagating -EPIPE (stall) to userspace since it is
|
|
|
|
* better handled as an empty read
|
cdc-wdm: Clear read pipeline in case of error
Implemented queued response handling. This queue is processed every time the
WDM_READ flag is cleared.
In case of a read error, userspace may not actually read the data, since the
driver returns an error through wdm_poll. After this, the underlying device may
attempt to send us more data, but the queue is not processed. While userspace is
also blocked, because the read error is never cleared.
After this patch, we proactively process the queue on a read error. If there was
an outstanding response to handle, that will clear the error (or go through the
same logic again, if another read error occurs). If there was no outstanding
response, this will bring the queue size back to 0, unblocking a future response
from the underlying device.
Signed-off-by: Robert Foss <robert.foss@collabora.com>
Tested-by: Robert Foss <robert.foss@collabora.com>
Acked-by: Oliver Neukum <oneukum@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-08-09 07:54:52 -07:00
|
|
|
*/
|
2017-09-22 13:18:18 -07:00
|
|
|
if (desc->rerr == 0 && status != -EPIPE)
|
cdc-wdm: Clear read pipeline in case of error
Implemented queued response handling. This queue is processed every time the
WDM_READ flag is cleared.
In case of a read error, userspace may not actually read the data, since the
driver returns an error through wdm_poll. After this, the underlying device may
attempt to send us more data, but the queue is not processed. While userspace is
also blocked, because the read error is never cleared.
After this patch, we proactively process the queue on a read error. If there was
an outstanding response to handle, that will clear the error (or go through the
same logic again, if another read error occurs). If there was no outstanding
response, this will bring the queue size back to 0, unblocking a future response
from the underlying device.
Signed-off-by: Robert Foss <robert.foss@collabora.com>
Tested-by: Robert Foss <robert.foss@collabora.com>
Acked-by: Oliver Neukum <oneukum@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-08-09 07:54:52 -07:00
|
|
|
desc->rerr = status;
|
|
|
|
|
2013-03-12 06:52:42 -07:00
|
|
|
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;
|
|
|
|
desc->reslength = length;
|
|
|
|
}
|
|
|
|
}
|
2010-02-27 12:54:59 -07:00
|
|
|
skip_error:
|
2008-05-13 08:01:25 -07:00
|
|
|
|
cdc-wdm: Clear read pipeline in case of error
Implemented queued response handling. This queue is processed every time the
WDM_READ flag is cleared.
In case of a read error, userspace may not actually read the data, since the
driver returns an error through wdm_poll. After this, the underlying device may
attempt to send us more data, but the queue is not processed. While userspace is
also blocked, because the read error is never cleared.
After this patch, we proactively process the queue on a read error. If there was
an outstanding response to handle, that will clear the error (or go through the
same logic again, if another read error occurs). If there was no outstanding
response, this will bring the queue size back to 0, unblocking a future response
from the underlying device.
Signed-off-by: Robert Foss <robert.foss@collabora.com>
Tested-by: Robert Foss <robert.foss@collabora.com>
Acked-by: Oliver Neukum <oneukum@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-08-09 07:54:52 -07:00
|
|
|
if (desc->rerr) {
|
|
|
|
/*
|
|
|
|
* Since there was 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.
|
|
|
|
*/
|
2018-06-14 09:36:46 -07:00
|
|
|
schedule_work(&desc->service_outs_intr);
|
|
|
|
} else {
|
|
|
|
set_bit(WDM_READ, &desc->flags);
|
|
|
|
wake_up(&desc->wait);
|
cdc-wdm: Clear read pipeline in case of error
Implemented queued response handling. This queue is processed every time the
WDM_READ flag is cleared.
In case of a read error, userspace may not actually read the data, since the
driver returns an error through wdm_poll. After this, the underlying device may
attempt to send us more data, but the queue is not processed. While userspace is
also blocked, because the read error is never cleared.
After this patch, we proactively process the queue on a read error. If there was
an outstanding response to handle, that will clear the error (or go through the
same logic again, if another read error occurs). If there was no outstanding
response, this will bring the queue size back to 0, unblocking a future response
from the underlying device.
Signed-off-by: Robert Foss <robert.foss@collabora.com>
Tested-by: Robert Foss <robert.foss@collabora.com>
Acked-by: Oliver Neukum <oneukum@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-08-09 07:54:52 -07:00
|
|
|
}
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
out:
|
2018-06-24 15:08:35 -07:00
|
|
|
spin_unlock_irqrestore(&desc->iuspin, flags);
|
2008-05-13 08:01:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void wdm_int_callback(struct urb *urb)
|
|
|
|
{
|
2018-06-24 15:08:35 -07:00
|
|
|
unsigned long flags;
|
2008-05-13 08:01:25 -07:00
|
|
|
int rv = 0;
|
2013-08-06 05:22:59 -07:00
|
|
|
int responding;
|
2008-05-13 08:01:25 -07:00
|
|
|
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);
|
2008-08-14 09:37:34 -07:00
|
|
|
dev_err(&desc->intf->dev, "Stall on int endpoint\n");
|
2008-05-13 08:01:25 -07:00
|
|
|
goto sw; /* halt is cleared in work */
|
|
|
|
default:
|
USB: class: cdc-wdm: Fix CPU lockup caused by excessive log messages
The syzbot fuzzer found that the interrupt-URB completion callback in
the cdc-wdm driver was taking too long, and the driver's immediate
resubmission of interrupt URBs with -EPROTO status combined with the
dummy-hcd emulation to cause a CPU lockup:
cdc_wdm 1-1:1.0: nonzero urb status received: -71
cdc_wdm 1-1:1.0: wdm_int_callback - 0 bytes
watchdog: BUG: soft lockup - CPU#0 stuck for 26s! [syz-executor782:6625]
CPU#0 Utilization every 4s during lockup:
#1: 98% system, 0% softirq, 3% hardirq, 0% idle
#2: 98% system, 0% softirq, 3% hardirq, 0% idle
#3: 98% system, 0% softirq, 3% hardirq, 0% idle
#4: 98% system, 0% softirq, 3% hardirq, 0% idle
#5: 98% system, 1% softirq, 3% hardirq, 0% idle
Modules linked in:
irq event stamp: 73096
hardirqs last enabled at (73095): [<ffff80008037bc00>] console_emit_next_record kernel/printk/printk.c:2935 [inline]
hardirqs last enabled at (73095): [<ffff80008037bc00>] console_flush_all+0x650/0xb74 kernel/printk/printk.c:2994
hardirqs last disabled at (73096): [<ffff80008af10b00>] __el1_irq arch/arm64/kernel/entry-common.c:533 [inline]
hardirqs last disabled at (73096): [<ffff80008af10b00>] el1_interrupt+0x24/0x68 arch/arm64/kernel/entry-common.c:551
softirqs last enabled at (73048): [<ffff8000801ea530>] softirq_handle_end kernel/softirq.c:400 [inline]
softirqs last enabled at (73048): [<ffff8000801ea530>] handle_softirqs+0xa60/0xc34 kernel/softirq.c:582
softirqs last disabled at (73043): [<ffff800080020de8>] __do_softirq+0x14/0x20 kernel/softirq.c:588
CPU: 0 PID: 6625 Comm: syz-executor782 Tainted: G W 6.10.0-rc2-syzkaller-g8867bbd4a056 #0
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/02/2024
Testing showed that the problem did not occur if the two error
messages -- the first two lines above -- were removed; apparently adding
material to the kernel log takes a surprisingly large amount of time.
In any case, the best approach for preventing these lockups and to
avoid spamming the log with thousands of error messages per second is
to ratelimit the two dev_err() calls. Therefore we replace them with
dev_err_ratelimited().
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Suggested-by: Greg KH <gregkh@linuxfoundation.org>
Reported-and-tested-by: syzbot+5f996b83575ef4058638@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/linux-usb/00000000000073d54b061a6a1c65@google.com/
Reported-and-tested-by: syzbot+1b2abad17596ad03dcff@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/linux-usb/000000000000f45085061aa9b37e@google.com/
Fixes: 9908a32e94de ("USB: remove err() macro from usb class drivers")
Link: https://lore.kernel.org/linux-usb/40dfa45b-5f21-4eef-a8c1-51a2f320e267@rowland.harvard.edu/
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/29855215-52f5-4385-b058-91f42c2bee18@rowland.harvard.edu
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2024-06-13 18:30:43 -07:00
|
|
|
dev_err_ratelimited(&desc->intf->dev,
|
2008-08-14 09:37:34 -07:00
|
|
|
"nonzero urb status received: %d\n", status);
|
2008-05-13 08:01:25 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (urb->actual_length < sizeof(struct usb_cdc_notification)) {
|
USB: class: cdc-wdm: Fix CPU lockup caused by excessive log messages
The syzbot fuzzer found that the interrupt-URB completion callback in
the cdc-wdm driver was taking too long, and the driver's immediate
resubmission of interrupt URBs with -EPROTO status combined with the
dummy-hcd emulation to cause a CPU lockup:
cdc_wdm 1-1:1.0: nonzero urb status received: -71
cdc_wdm 1-1:1.0: wdm_int_callback - 0 bytes
watchdog: BUG: soft lockup - CPU#0 stuck for 26s! [syz-executor782:6625]
CPU#0 Utilization every 4s during lockup:
#1: 98% system, 0% softirq, 3% hardirq, 0% idle
#2: 98% system, 0% softirq, 3% hardirq, 0% idle
#3: 98% system, 0% softirq, 3% hardirq, 0% idle
#4: 98% system, 0% softirq, 3% hardirq, 0% idle
#5: 98% system, 1% softirq, 3% hardirq, 0% idle
Modules linked in:
irq event stamp: 73096
hardirqs last enabled at (73095): [<ffff80008037bc00>] console_emit_next_record kernel/printk/printk.c:2935 [inline]
hardirqs last enabled at (73095): [<ffff80008037bc00>] console_flush_all+0x650/0xb74 kernel/printk/printk.c:2994
hardirqs last disabled at (73096): [<ffff80008af10b00>] __el1_irq arch/arm64/kernel/entry-common.c:533 [inline]
hardirqs last disabled at (73096): [<ffff80008af10b00>] el1_interrupt+0x24/0x68 arch/arm64/kernel/entry-common.c:551
softirqs last enabled at (73048): [<ffff8000801ea530>] softirq_handle_end kernel/softirq.c:400 [inline]
softirqs last enabled at (73048): [<ffff8000801ea530>] handle_softirqs+0xa60/0xc34 kernel/softirq.c:582
softirqs last disabled at (73043): [<ffff800080020de8>] __do_softirq+0x14/0x20 kernel/softirq.c:588
CPU: 0 PID: 6625 Comm: syz-executor782 Tainted: G W 6.10.0-rc2-syzkaller-g8867bbd4a056 #0
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/02/2024
Testing showed that the problem did not occur if the two error
messages -- the first two lines above -- were removed; apparently adding
material to the kernel log takes a surprisingly large amount of time.
In any case, the best approach for preventing these lockups and to
avoid spamming the log with thousands of error messages per second is
to ratelimit the two dev_err() calls. Therefore we replace them with
dev_err_ratelimited().
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Suggested-by: Greg KH <gregkh@linuxfoundation.org>
Reported-and-tested-by: syzbot+5f996b83575ef4058638@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/linux-usb/00000000000073d54b061a6a1c65@google.com/
Reported-and-tested-by: syzbot+1b2abad17596ad03dcff@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/linux-usb/000000000000f45085061aa9b37e@google.com/
Fixes: 9908a32e94de ("USB: remove err() macro from usb class drivers")
Link: https://lore.kernel.org/linux-usb/40dfa45b-5f21-4eef-a8c1-51a2f320e267@rowland.harvard.edu/
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/29855215-52f5-4385-b058-91f42c2bee18@rowland.harvard.edu
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2024-06-13 18:30:43 -07:00
|
|
|
dev_err_ratelimited(&desc->intf->dev, "wdm_int_callback - %d bytes\n",
|
2008-08-14 09:37:34 -07:00
|
|
|
urb->actual_length);
|
2008-05-13 08:01:25 -07:00
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (dr->bNotificationType) {
|
|
|
|
case USB_CDC_NOTIFY_RESPONSE_AVAILABLE:
|
|
|
|
dev_dbg(&desc->intf->dev,
|
2016-08-16 06:12:22 -07:00
|
|
|
"NOTIFY_RESPONSE_AVAILABLE received: index %d len %d\n",
|
2015-03-20 06:29:34 -07:00
|
|
|
le16_to_cpu(dr->wIndex), le16_to_cpu(dr->wLength));
|
2008-05-13 08:01:25 -07:00
|
|
|
break;
|
|
|
|
|
|
|
|
case USB_CDC_NOTIFY_NETWORK_CONNECTION:
|
|
|
|
|
|
|
|
dev_dbg(&desc->intf->dev,
|
2016-08-16 06:12:22 -07:00
|
|
|
"NOTIFY_NETWORK_CONNECTION %s network\n",
|
2008-05-13 08:01:25 -07:00
|
|
|
dr->wValue ? "connected to" : "disconnected from");
|
|
|
|
goto exit;
|
2013-10-29 01:52:57 -07:00
|
|
|
case USB_CDC_NOTIFY_SPEED_CHANGE:
|
2016-08-16 06:12:22 -07:00
|
|
|
dev_dbg(&desc->intf->dev, "SPEED_CHANGE received (len %u)\n",
|
2013-10-29 01:52:57 -07:00
|
|
|
urb->actual_length);
|
|
|
|
goto exit;
|
2008-05-13 08:01:25 -07:00
|
|
|
default:
|
|
|
|
clear_bit(WDM_POLL_RUNNING, &desc->flags);
|
2008-08-14 09:37:34 -07:00
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"unknown notification %d received: index %d len %d\n",
|
2015-03-20 06:29:34 -07:00
|
|
|
dr->bNotificationType,
|
|
|
|
le16_to_cpu(dr->wIndex),
|
|
|
|
le16_to_cpu(dr->wLength));
|
2008-05-13 08:01:25 -07:00
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
2018-06-24 15:08:35 -07:00
|
|
|
spin_lock_irqsave(&desc->iuspin, flags);
|
2013-08-06 05:22:59 -07:00
|
|
|
responding = test_and_set_bit(WDM_RESPONDING, &desc->flags);
|
2013-10-29 10:29:10 -07:00
|
|
|
if (!desc->resp_count++ && !responding
|
|
|
|
&& !test_bit(WDM_DISCONNECTING, &desc->flags)
|
2010-02-27 12:55:52 -07:00
|
|
|
&& !test_bit(WDM_SUSPENDING, &desc->flags)) {
|
2008-05-13 08:01:25 -07:00
|
|
|
rv = usb_submit_urb(desc->response, GFP_ATOMIC);
|
2016-08-16 06:12:22 -07:00
|
|
|
dev_dbg(&desc->intf->dev, "submit response URB %d\n", rv);
|
2008-05-13 08:01:25 -07:00
|
|
|
}
|
2018-06-24 15:08:35 -07:00
|
|
|
spin_unlock_irqrestore(&desc->iuspin, flags);
|
2008-05-13 08:01:25 -07:00
|
|
|
if (rv < 0) {
|
2010-02-27 12:54:59 -07:00
|
|
|
clear_bit(WDM_RESPONDING, &desc->flags);
|
2008-05-13 08:01:25 -07:00
|
|
|
if (rv == -EPERM)
|
|
|
|
return;
|
|
|
|
if (rv == -ENOMEM) {
|
|
|
|
sw:
|
|
|
|
rv = schedule_work(&desc->rxwork);
|
|
|
|
if (rv)
|
2008-08-14 09:37:34 -07:00
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"Cannot schedule work\n");
|
2008-05-13 08:01:25 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
exit:
|
|
|
|
rv = usb_submit_urb(urb, GFP_ATOMIC);
|
|
|
|
if (rv)
|
2008-08-14 09:37:34 -07:00
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"%s - usb_submit_urb failed with result %d\n",
|
|
|
|
__func__, rv);
|
2008-05-13 08:01:25 -07:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-04-26 02:26:22 -07:00
|
|
|
static void poison_urbs(struct wdm_device *desc)
|
2008-05-13 08:01:25 -07:00
|
|
|
{
|
2008-06-24 06:56:10 -07:00
|
|
|
/* the order here is essential */
|
2021-04-26 02:26:22 -07:00
|
|
|
usb_poison_urb(desc->command);
|
|
|
|
usb_poison_urb(desc->validity);
|
|
|
|
usb_poison_urb(desc->response);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void 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);
|
2008-05-13 08:01:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void free_urbs(struct wdm_device *desc)
|
|
|
|
{
|
|
|
|
usb_free_urb(desc->validity);
|
|
|
|
usb_free_urb(desc->response);
|
|
|
|
usb_free_urb(desc->command);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cleanup(struct wdm_device *desc)
|
|
|
|
{
|
2012-01-16 07:12:00 -07:00
|
|
|
kfree(desc->sbuf);
|
|
|
|
kfree(desc->inbuf);
|
2008-05-13 08:01:25 -07:00
|
|
|
kfree(desc->orq);
|
|
|
|
kfree(desc->irq);
|
|
|
|
kfree(desc->ubuf);
|
|
|
|
free_urbs(desc);
|
|
|
|
kfree(desc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t wdm_write
|
|
|
|
(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
|
|
|
|
{
|
|
|
|
u8 *buf;
|
|
|
|
int rv = -EMSGSIZE, r, we;
|
|
|
|
struct wdm_device *desc = file->private_data;
|
|
|
|
struct usb_ctrlrequest *req;
|
|
|
|
|
|
|
|
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)
|
2015-03-20 06:28:56 -07:00
|
|
|
return usb_translate_errors(we);
|
2008-05-13 08:01:25 -07:00
|
|
|
|
2017-05-08 08:14:39 -07:00
|
|
|
buf = memdup_user(buffer, count);
|
|
|
|
if (IS_ERR(buf))
|
|
|
|
return PTR_ERR(buf);
|
2010-02-27 12:54:24 -07:00
|
|
|
|
|
|
|
/* concurrent writes and disconnect */
|
2012-01-16 04:41:48 -07:00
|
|
|
r = mutex_lock_interruptible(&desc->wlock);
|
2008-05-13 08:01:25 -07:00
|
|
|
rv = -ERESTARTSYS;
|
2015-03-20 06:29:18 -07:00
|
|
|
if (r)
|
|
|
|
goto out_free_mem;
|
2010-02-27 12:54:24 -07:00
|
|
|
|
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
|
|
|
|
rv = -ENODEV;
|
2015-03-20 06:29:18 -07:00
|
|
|
goto out_free_mem_lock;
|
2010-02-27 12:54:24 -07:00
|
|
|
}
|
2008-05-13 08:01:25 -07:00
|
|
|
|
2008-06-24 06:56:10 -07:00
|
|
|
r = usb_autopm_get_interface(desc->intf);
|
2010-02-27 12:54:24 -07:00
|
|
|
if (r < 0) {
|
2012-04-30 00:57:31 -07:00
|
|
|
rv = usb_translate_errors(r);
|
2015-03-20 06:29:18 -07:00
|
|
|
goto out_free_mem_lock;
|
2010-02-27 12:54:24 -07:00
|
|
|
}
|
2009-09-09 01:12:48 -07:00
|
|
|
|
2010-12-27 10:49:58 -07:00
|
|
|
if (!(file->f_flags & O_NONBLOCK))
|
2009-09-09 01:12:48 -07:00
|
|
|
r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE,
|
|
|
|
&desc->flags));
|
|
|
|
else
|
|
|
|
if (test_bit(WDM_IN_USE, &desc->flags))
|
|
|
|
r = -EAGAIN;
|
2012-02-10 01:44:08 -07:00
|
|
|
|
|
|
|
if (test_bit(WDM_RESETTING, &desc->flags))
|
|
|
|
r = -EIO;
|
|
|
|
|
USB: cdc-wdm: Make wdm_flush() interruptible and add wdm_fsync().
syzbot is reporting hung task at wdm_flush() [1], for there is a circular
dependency that wdm_flush() from flip_close() for /dev/cdc-wdm0 forever
waits for /dev/raw-gadget to be closed while close() for /dev/raw-gadget
cannot be called unless close() for /dev/cdc-wdm0 completes.
Tetsuo Handa considered that such circular dependency is an usage error [2]
which corresponds to an unresponding broken hardware [3]. But Alan Stern
responded that we should be prepared for such hardware [4]. Therefore,
this patch changes wdm_flush() to use wait_event_interruptible_timeout()
which gives up after 30 seconds, for hardware that remains silent must be
ignored. The 30 seconds are coming out of thin air.
Changing wait_event() to wait_event_interruptible_timeout() makes error
reporting from close() syscall less reliable. To compensate it, this patch
also implements wdm_fsync() which does not use timeout. Those who want to
be very sure that data has gone out to the device are now advised to call
fsync(), with a caveat that fsync() can return -EINVAL when running on
older kernels which do not implement wdm_fsync().
This patch also fixes three more problems (listed below) found during
exhaustive discussion and testing.
Since multiple threads can concurrently call wdm_write()/wdm_flush(),
we need to use wake_up_all() whenever clearing WDM_IN_USE in order to
make sure that all waiters are woken up. Also, error reporting needs
to use fetch-and-clear approach in order not to report same error for
multiple times.
Since wdm_flush() checks WDM_DISCONNECTING, wdm_write() should as well
check WDM_DISCONNECTING.
In wdm_flush(), since locks are not held, it is not safe to dereference
desc->intf after checking that WDM_DISCONNECTING is not set [5]. Thus,
remove dev_err() from wdm_flush().
[1] https://syzkaller.appspot.com/bug?id=e7b761593b23eb50855b9ea31e3be5472b711186
[2] https://lkml.kernel.org/r/27b7545e-8f41-10b8-7c02-e35a08eb1611@i-love.sakura.ne.jp
[3] https://lkml.kernel.org/r/79ba410f-e0ef-2465-b94f-6b9a4a82adf5@i-love.sakura.ne.jp
[4] https://lkml.kernel.org/r/20200530011040.GB12419@rowland.harvard.edu
[5] https://lkml.kernel.org/r/c85331fc-874c-6e46-a77f-0ef1dc075308@i-love.sakura.ne.jp
Reported-by: syzbot <syzbot+854768b99f19e89d7f81@syzkaller.appspotmail.com>
Cc: stable <stable@vger.kernel.org>
Co-developed-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Oliver Neukum <oneukum@suse.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Link: https://lore.kernel.org/r/20200928141755.3476-1-penguin-kernel@I-love.SAKURA.ne.jp
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-09-28 07:17:55 -07:00
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags))
|
|
|
|
r = -ENODEV;
|
|
|
|
|
2010-02-27 12:54:24 -07:00
|
|
|
if (r < 0) {
|
2012-04-30 00:57:31 -07:00
|
|
|
rv = r;
|
2015-03-20 06:29:18 -07:00
|
|
|
goto out_free_mem_pm;
|
2008-05-13 08:01:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
req = desc->orq;
|
|
|
|
usb_fill_control_urb(
|
|
|
|
desc->command,
|
|
|
|
interface_to_usbdev(desc->intf),
|
|
|
|
/* using common endpoint 0 */
|
|
|
|
usb_sndctrlpipe(interface_to_usbdev(desc->intf), 0),
|
|
|
|
(unsigned char *)req,
|
|
|
|
buf,
|
|
|
|
count,
|
|
|
|
wdm_out_callback,
|
|
|
|
desc
|
|
|
|
);
|
|
|
|
|
|
|
|
req->bRequestType = (USB_DIR_OUT | USB_TYPE_CLASS |
|
|
|
|
USB_RECIP_INTERFACE);
|
|
|
|
req->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND;
|
|
|
|
req->wValue = 0;
|
2015-03-20 06:29:34 -07:00
|
|
|
req->wIndex = desc->inum; /* already converted */
|
2008-05-13 08:01:25 -07:00
|
|
|
req->wLength = cpu_to_le16(count);
|
|
|
|
set_bit(WDM_IN_USE, &desc->flags);
|
2012-04-26 12:59:10 -07:00
|
|
|
desc->outbuf = buf;
|
2008-05-13 08:01:25 -07:00
|
|
|
|
|
|
|
rv = usb_submit_urb(desc->command, GFP_KERNEL);
|
|
|
|
if (rv < 0) {
|
2012-04-26 12:59:10 -07:00
|
|
|
desc->outbuf = NULL;
|
2008-05-13 08:01:25 -07:00
|
|
|
clear_bit(WDM_IN_USE, &desc->flags);
|
USB: cdc-wdm: Make wdm_flush() interruptible and add wdm_fsync().
syzbot is reporting hung task at wdm_flush() [1], for there is a circular
dependency that wdm_flush() from flip_close() for /dev/cdc-wdm0 forever
waits for /dev/raw-gadget to be closed while close() for /dev/raw-gadget
cannot be called unless close() for /dev/cdc-wdm0 completes.
Tetsuo Handa considered that such circular dependency is an usage error [2]
which corresponds to an unresponding broken hardware [3]. But Alan Stern
responded that we should be prepared for such hardware [4]. Therefore,
this patch changes wdm_flush() to use wait_event_interruptible_timeout()
which gives up after 30 seconds, for hardware that remains silent must be
ignored. The 30 seconds are coming out of thin air.
Changing wait_event() to wait_event_interruptible_timeout() makes error
reporting from close() syscall less reliable. To compensate it, this patch
also implements wdm_fsync() which does not use timeout. Those who want to
be very sure that data has gone out to the device are now advised to call
fsync(), with a caveat that fsync() can return -EINVAL when running on
older kernels which do not implement wdm_fsync().
This patch also fixes three more problems (listed below) found during
exhaustive discussion and testing.
Since multiple threads can concurrently call wdm_write()/wdm_flush(),
we need to use wake_up_all() whenever clearing WDM_IN_USE in order to
make sure that all waiters are woken up. Also, error reporting needs
to use fetch-and-clear approach in order not to report same error for
multiple times.
Since wdm_flush() checks WDM_DISCONNECTING, wdm_write() should as well
check WDM_DISCONNECTING.
In wdm_flush(), since locks are not held, it is not safe to dereference
desc->intf after checking that WDM_DISCONNECTING is not set [5]. Thus,
remove dev_err() from wdm_flush().
[1] https://syzkaller.appspot.com/bug?id=e7b761593b23eb50855b9ea31e3be5472b711186
[2] https://lkml.kernel.org/r/27b7545e-8f41-10b8-7c02-e35a08eb1611@i-love.sakura.ne.jp
[3] https://lkml.kernel.org/r/79ba410f-e0ef-2465-b94f-6b9a4a82adf5@i-love.sakura.ne.jp
[4] https://lkml.kernel.org/r/20200530011040.GB12419@rowland.harvard.edu
[5] https://lkml.kernel.org/r/c85331fc-874c-6e46-a77f-0ef1dc075308@i-love.sakura.ne.jp
Reported-by: syzbot <syzbot+854768b99f19e89d7f81@syzkaller.appspotmail.com>
Cc: stable <stable@vger.kernel.org>
Co-developed-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Oliver Neukum <oneukum@suse.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Link: https://lore.kernel.org/r/20200928141755.3476-1-penguin-kernel@I-love.SAKURA.ne.jp
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-09-28 07:17:55 -07:00
|
|
|
wake_up_all(&desc->wait); /* for wdm_wait_for_response() */
|
2008-08-14 09:37:34 -07:00
|
|
|
dev_err(&desc->intf->dev, "Tx URB error: %d\n", rv);
|
2012-04-30 00:57:31 -07:00
|
|
|
rv = usb_translate_errors(rv);
|
2015-03-20 06:29:18 -07:00
|
|
|
goto out_free_mem_pm;
|
2008-05-13 08:01:25 -07:00
|
|
|
} else {
|
2016-08-16 06:12:22 -07:00
|
|
|
dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d\n",
|
2015-03-20 06:29:34 -07:00
|
|
|
le16_to_cpu(req->wIndex));
|
2008-05-13 08:01:25 -07:00
|
|
|
}
|
2015-03-20 06:29:18 -07:00
|
|
|
|
2008-06-24 06:56:10 -07:00
|
|
|
usb_autopm_put_interface(desc->intf);
|
2012-01-16 04:41:48 -07:00
|
|
|
mutex_unlock(&desc->wlock);
|
2017-05-08 08:14:39 -07:00
|
|
|
return count;
|
2015-03-20 06:29:18 -07:00
|
|
|
|
|
|
|
out_free_mem_pm:
|
|
|
|
usb_autopm_put_interface(desc->intf);
|
|
|
|
out_free_mem_lock:
|
|
|
|
mutex_unlock(&desc->wlock);
|
|
|
|
out_free_mem:
|
|
|
|
kfree(buf);
|
|
|
|
return rv;
|
2008-05-13 08:01:25 -07:00
|
|
|
}
|
|
|
|
|
2013-12-20 06:07:24 -07:00
|
|
|
/*
|
cdc-wdm: Clear read pipeline in case of error
Implemented queued response handling. This queue is processed every time the
WDM_READ flag is cleared.
In case of a read error, userspace may not actually read the data, since the
driver returns an error through wdm_poll. After this, the underlying device may
attempt to send us more data, but the queue is not processed. While userspace is
also blocked, because the read error is never cleared.
After this patch, we proactively process the queue on a read error. If there was
an outstanding response to handle, that will clear the error (or go through the
same logic again, if another read error occurs). If there was no outstanding
response, this will bring the queue size back to 0, unblocking a future response
from the underlying device.
Signed-off-by: Robert Foss <robert.foss@collabora.com>
Tested-by: Robert Foss <robert.foss@collabora.com>
Acked-by: Oliver Neukum <oneukum@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-08-09 07:54:52 -07:00
|
|
|
* Submit the read urb if resp_count is non-zero.
|
2013-12-20 06:07:24 -07:00
|
|
|
*
|
|
|
|
* Called with desc->iuspin locked
|
|
|
|
*/
|
cdc-wdm: Clear read pipeline in case of error
Implemented queued response handling. This queue is processed every time the
WDM_READ flag is cleared.
In case of a read error, userspace may not actually read the data, since the
driver returns an error through wdm_poll. After this, the underlying device may
attempt to send us more data, but the queue is not processed. While userspace is
also blocked, because the read error is never cleared.
After this patch, we proactively process the queue on a read error. If there was
an outstanding response to handle, that will clear the error (or go through the
same logic again, if another read error occurs). If there was no outstanding
response, this will bring the queue size back to 0, unblocking a future response
from the underlying device.
Signed-off-by: Robert Foss <robert.foss@collabora.com>
Tested-by: Robert Foss <robert.foss@collabora.com>
Acked-by: Oliver Neukum <oneukum@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-08-09 07:54:52 -07:00
|
|
|
static int service_outstanding_interrupt(struct wdm_device *desc)
|
2013-12-20 06:07:24 -07:00
|
|
|
{
|
|
|
|
int rv = 0;
|
|
|
|
|
|
|
|
/* submit read urb only if the device is waiting for it */
|
2014-01-12 13:48:53 -07:00
|
|
|
if (!desc->resp_count || !--desc->resp_count)
|
2013-12-20 06:07:24 -07:00
|
|
|
goto out;
|
|
|
|
|
2020-12-19 08:25:53 -07:00
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
|
|
|
|
rv = -ENODEV;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
if (test_bit(WDM_RESETTING, &desc->flags)) {
|
|
|
|
rv = -EIO;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2024-04-18 07:33:28 -07:00
|
|
|
set_bit(WDM_RESPONDING, &desc->flags);
|
2013-12-20 06:07:24 -07:00
|
|
|
spin_unlock_irq(&desc->iuspin);
|
2018-09-11 01:00:44 -07:00
|
|
|
rv = usb_submit_urb(desc->response, GFP_KERNEL);
|
2013-12-20 06:07:24 -07:00
|
|
|
spin_lock_irq(&desc->iuspin);
|
|
|
|
if (rv) {
|
2020-12-19 08:25:53 -07:00
|
|
|
if (!test_bit(WDM_DISCONNECTING, &desc->flags))
|
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"usb_submit_urb failed with result %d\n", rv);
|
2013-12-20 06:07:24 -07:00
|
|
|
|
|
|
|
/* make sure the next notification trigger a submit */
|
|
|
|
clear_bit(WDM_RESPONDING, &desc->flags);
|
|
|
|
desc->resp_count = 0;
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
static ssize_t wdm_read
|
|
|
|
(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
|
|
|
|
{
|
2012-02-11 23:00:41 -07:00
|
|
|
int rv, cntr;
|
2008-05-13 08:01:25 -07:00
|
|
|
int i = 0;
|
|
|
|
struct wdm_device *desc = file->private_data;
|
|
|
|
|
|
|
|
|
2012-01-16 04:41:48 -07:00
|
|
|
rv = mutex_lock_interruptible(&desc->rlock); /*concurrent reads */
|
2008-05-13 08:01:25 -07:00
|
|
|
if (rv < 0)
|
|
|
|
return -ERESTARTSYS;
|
|
|
|
|
locking/atomics: COCCINELLE/treewide: Convert trivial ACCESS_ONCE() patterns to READ_ONCE()/WRITE_ONCE()
Please do not apply this to mainline directly, instead please re-run the
coccinelle script shown below and apply its output.
For several reasons, it is desirable to use {READ,WRITE}_ONCE() in
preference to ACCESS_ONCE(), and new code is expected to use one of the
former. So far, there's been no reason to change most existing uses of
ACCESS_ONCE(), as these aren't harmful, and changing them results in
churn.
However, for some features, the read/write distinction is critical to
correct operation. To distinguish these cases, separate read/write
accessors must be used. This patch migrates (most) remaining
ACCESS_ONCE() instances to {READ,WRITE}_ONCE(), using the following
coccinelle script:
----
// Convert trivial ACCESS_ONCE() uses to equivalent READ_ONCE() and
// WRITE_ONCE()
// $ make coccicheck COCCI=/home/mark/once.cocci SPFLAGS="--include-headers" MODE=patch
virtual patch
@ depends on patch @
expression E1, E2;
@@
- ACCESS_ONCE(E1) = E2
+ WRITE_ONCE(E1, E2)
@ depends on patch @
expression E;
@@
- ACCESS_ONCE(E)
+ READ_ONCE(E)
----
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: davem@davemloft.net
Cc: linux-arch@vger.kernel.org
Cc: mpe@ellerman.id.au
Cc: shuah@kernel.org
Cc: snitzer@redhat.com
Cc: thor.thayer@linux.intel.com
Cc: tj@kernel.org
Cc: viro@zeniv.linux.org.uk
Cc: will.deacon@arm.com
Link: http://lkml.kernel.org/r/1508792849-3115-19-git-send-email-paulmck@linux.vnet.ibm.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
2017-10-23 14:07:29 -07:00
|
|
|
cntr = READ_ONCE(desc->length);
|
2012-02-11 23:00:41 -07:00
|
|
|
if (cntr == 0) {
|
2008-05-13 08:01:25 -07:00
|
|
|
desc->read = 0;
|
|
|
|
retry:
|
2009-09-09 01:12:48 -07:00
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
|
|
|
|
rv = -ENODEV;
|
|
|
|
goto err;
|
|
|
|
}
|
2013-03-12 06:52:42 -07:00
|
|
|
if (test_bit(WDM_OVERFLOW, &desc->flags)) {
|
|
|
|
clear_bit(WDM_OVERFLOW, &desc->flags);
|
|
|
|
rv = -ENOBUFS;
|
|
|
|
goto err;
|
|
|
|
}
|
2008-05-13 08:01:25 -07:00
|
|
|
i++;
|
2009-09-09 01:12:48 -07:00
|
|
|
if (file->f_flags & O_NONBLOCK) {
|
|
|
|
if (!test_bit(WDM_READ, &desc->flags)) {
|
2017-02-14 20:10:52 -07:00
|
|
|
rv = -EAGAIN;
|
2009-09-09 01:12:48 -07:00
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
rv = 0;
|
|
|
|
} else {
|
|
|
|
rv = wait_event_interruptible(desc->wait,
|
|
|
|
test_bit(WDM_READ, &desc->flags));
|
|
|
|
}
|
2008-05-13 08:01:25 -07:00
|
|
|
|
2009-09-09 01:12:48 -07:00
|
|
|
/* may have happened while we slept */
|
2008-06-24 06:56:10 -07:00
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
|
|
|
|
rv = -ENODEV;
|
|
|
|
goto err;
|
|
|
|
}
|
2012-02-10 01:44:08 -07:00
|
|
|
if (test_bit(WDM_RESETTING, &desc->flags)) {
|
|
|
|
rv = -EIO;
|
|
|
|
goto err;
|
|
|
|
}
|
2008-06-24 06:56:10 -07:00
|
|
|
usb_mark_last_busy(interface_to_usbdev(desc->intf));
|
2008-05-13 08:01:25 -07:00
|
|
|
if (rv < 0) {
|
|
|
|
rv = -ERESTARTSYS;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock_irq(&desc->iuspin);
|
|
|
|
|
|
|
|
if (desc->rerr) { /* read completed, error happened */
|
2015-03-23 06:34:43 -07:00
|
|
|
rv = usb_translate_errors(desc->rerr);
|
2008-05-13 08:01:25 -07:00
|
|
|
desc->rerr = 0;
|
|
|
|
spin_unlock_irq(&desc->iuspin);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* recheck whether we've lost the race
|
|
|
|
* against the completion handler
|
|
|
|
*/
|
|
|
|
if (!test_bit(WDM_READ, &desc->flags)) { /* lost race */
|
|
|
|
spin_unlock_irq(&desc->iuspin);
|
|
|
|
goto retry;
|
|
|
|
}
|
2013-03-12 06:52:42 -07:00
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
if (!desc->reslength) { /* zero length read */
|
2016-08-16 06:12:22 -07:00
|
|
|
dev_dbg(&desc->intf->dev, "zero length - clearing WDM_READ\n");
|
cdc-wdm: Clear read pipeline in case of error
Implemented queued response handling. This queue is processed every time the
WDM_READ flag is cleared.
In case of a read error, userspace may not actually read the data, since the
driver returns an error through wdm_poll. After this, the underlying device may
attempt to send us more data, but the queue is not processed. While userspace is
also blocked, because the read error is never cleared.
After this patch, we proactively process the queue on a read error. If there was
an outstanding response to handle, that will clear the error (or go through the
same logic again, if another read error occurs). If there was no outstanding
response, this will bring the queue size back to 0, unblocking a future response
from the underlying device.
Signed-off-by: Robert Foss <robert.foss@collabora.com>
Tested-by: Robert Foss <robert.foss@collabora.com>
Acked-by: Oliver Neukum <oneukum@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-08-09 07:54:52 -07:00
|
|
|
clear_bit(WDM_READ, &desc->flags);
|
|
|
|
rv = service_outstanding_interrupt(desc);
|
2008-05-13 08:01:25 -07:00
|
|
|
spin_unlock_irq(&desc->iuspin);
|
2013-12-20 06:07:24 -07:00
|
|
|
if (rv < 0)
|
|
|
|
goto err;
|
2008-05-13 08:01:25 -07:00
|
|
|
goto retry;
|
|
|
|
}
|
2012-02-11 23:00:41 -07:00
|
|
|
cntr = desc->length;
|
2008-05-13 08:01:25 -07:00
|
|
|
spin_unlock_irq(&desc->iuspin);
|
|
|
|
}
|
|
|
|
|
2012-02-11 23:00:41 -07:00
|
|
|
if (cntr > count)
|
|
|
|
cntr = count;
|
2008-05-13 08:01:25 -07:00
|
|
|
rv = copy_to_user(buffer, desc->ubuf, cntr);
|
|
|
|
if (rv > 0) {
|
|
|
|
rv = -EFAULT;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2012-02-11 23:00:41 -07:00
|
|
|
spin_lock_irq(&desc->iuspin);
|
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
for (i = 0; i < desc->length - cntr; i++)
|
|
|
|
desc->ubuf[i] = desc->ubuf[i + cntr];
|
|
|
|
|
|
|
|
desc->length -= cntr;
|
2008-06-19 05:20:18 -07:00
|
|
|
/* in case we had outstanding data */
|
cdc-wdm: Clear read pipeline in case of error
Implemented queued response handling. This queue is processed every time the
WDM_READ flag is cleared.
In case of a read error, userspace may not actually read the data, since the
driver returns an error through wdm_poll. After this, the underlying device may
attempt to send us more data, but the queue is not processed. While userspace is
also blocked, because the read error is never cleared.
After this patch, we proactively process the queue on a read error. If there was
an outstanding response to handle, that will clear the error (or go through the
same logic again, if another read error occurs). If there was no outstanding
response, this will bring the queue size back to 0, unblocking a future response
from the underlying device.
Signed-off-by: Robert Foss <robert.foss@collabora.com>
Tested-by: Robert Foss <robert.foss@collabora.com>
Acked-by: Oliver Neukum <oneukum@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-08-09 07:54:52 -07:00
|
|
|
if (!desc->length) {
|
|
|
|
clear_bit(WDM_READ, &desc->flags);
|
|
|
|
service_outstanding_interrupt(desc);
|
|
|
|
}
|
2013-12-20 06:07:24 -07:00
|
|
|
spin_unlock_irq(&desc->iuspin);
|
2008-05-13 08:01:25 -07:00
|
|
|
rv = cntr;
|
|
|
|
|
|
|
|
err:
|
2012-01-16 04:41:48 -07:00
|
|
|
mutex_unlock(&desc->rlock);
|
2008-05-13 08:01:25 -07:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
USB: cdc-wdm: Make wdm_flush() interruptible and add wdm_fsync().
syzbot is reporting hung task at wdm_flush() [1], for there is a circular
dependency that wdm_flush() from flip_close() for /dev/cdc-wdm0 forever
waits for /dev/raw-gadget to be closed while close() for /dev/raw-gadget
cannot be called unless close() for /dev/cdc-wdm0 completes.
Tetsuo Handa considered that such circular dependency is an usage error [2]
which corresponds to an unresponding broken hardware [3]. But Alan Stern
responded that we should be prepared for such hardware [4]. Therefore,
this patch changes wdm_flush() to use wait_event_interruptible_timeout()
which gives up after 30 seconds, for hardware that remains silent must be
ignored. The 30 seconds are coming out of thin air.
Changing wait_event() to wait_event_interruptible_timeout() makes error
reporting from close() syscall less reliable. To compensate it, this patch
also implements wdm_fsync() which does not use timeout. Those who want to
be very sure that data has gone out to the device are now advised to call
fsync(), with a caveat that fsync() can return -EINVAL when running on
older kernels which do not implement wdm_fsync().
This patch also fixes three more problems (listed below) found during
exhaustive discussion and testing.
Since multiple threads can concurrently call wdm_write()/wdm_flush(),
we need to use wake_up_all() whenever clearing WDM_IN_USE in order to
make sure that all waiters are woken up. Also, error reporting needs
to use fetch-and-clear approach in order not to report same error for
multiple times.
Since wdm_flush() checks WDM_DISCONNECTING, wdm_write() should as well
check WDM_DISCONNECTING.
In wdm_flush(), since locks are not held, it is not safe to dereference
desc->intf after checking that WDM_DISCONNECTING is not set [5]. Thus,
remove dev_err() from wdm_flush().
[1] https://syzkaller.appspot.com/bug?id=e7b761593b23eb50855b9ea31e3be5472b711186
[2] https://lkml.kernel.org/r/27b7545e-8f41-10b8-7c02-e35a08eb1611@i-love.sakura.ne.jp
[3] https://lkml.kernel.org/r/79ba410f-e0ef-2465-b94f-6b9a4a82adf5@i-love.sakura.ne.jp
[4] https://lkml.kernel.org/r/20200530011040.GB12419@rowland.harvard.edu
[5] https://lkml.kernel.org/r/c85331fc-874c-6e46-a77f-0ef1dc075308@i-love.sakura.ne.jp
Reported-by: syzbot <syzbot+854768b99f19e89d7f81@syzkaller.appspotmail.com>
Cc: stable <stable@vger.kernel.org>
Co-developed-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Oliver Neukum <oneukum@suse.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Link: https://lore.kernel.org/r/20200928141755.3476-1-penguin-kernel@I-love.SAKURA.ne.jp
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-09-28 07:17:55 -07:00
|
|
|
static int wdm_wait_for_response(struct file *file, long timeout)
|
2008-05-13 08:01:25 -07:00
|
|
|
{
|
|
|
|
struct wdm_device *desc = file->private_data;
|
USB: cdc-wdm: Make wdm_flush() interruptible and add wdm_fsync().
syzbot is reporting hung task at wdm_flush() [1], for there is a circular
dependency that wdm_flush() from flip_close() for /dev/cdc-wdm0 forever
waits for /dev/raw-gadget to be closed while close() for /dev/raw-gadget
cannot be called unless close() for /dev/cdc-wdm0 completes.
Tetsuo Handa considered that such circular dependency is an usage error [2]
which corresponds to an unresponding broken hardware [3]. But Alan Stern
responded that we should be prepared for such hardware [4]. Therefore,
this patch changes wdm_flush() to use wait_event_interruptible_timeout()
which gives up after 30 seconds, for hardware that remains silent must be
ignored. The 30 seconds are coming out of thin air.
Changing wait_event() to wait_event_interruptible_timeout() makes error
reporting from close() syscall less reliable. To compensate it, this patch
also implements wdm_fsync() which does not use timeout. Those who want to
be very sure that data has gone out to the device are now advised to call
fsync(), with a caveat that fsync() can return -EINVAL when running on
older kernels which do not implement wdm_fsync().
This patch also fixes three more problems (listed below) found during
exhaustive discussion and testing.
Since multiple threads can concurrently call wdm_write()/wdm_flush(),
we need to use wake_up_all() whenever clearing WDM_IN_USE in order to
make sure that all waiters are woken up. Also, error reporting needs
to use fetch-and-clear approach in order not to report same error for
multiple times.
Since wdm_flush() checks WDM_DISCONNECTING, wdm_write() should as well
check WDM_DISCONNECTING.
In wdm_flush(), since locks are not held, it is not safe to dereference
desc->intf after checking that WDM_DISCONNECTING is not set [5]. Thus,
remove dev_err() from wdm_flush().
[1] https://syzkaller.appspot.com/bug?id=e7b761593b23eb50855b9ea31e3be5472b711186
[2] https://lkml.kernel.org/r/27b7545e-8f41-10b8-7c02-e35a08eb1611@i-love.sakura.ne.jp
[3] https://lkml.kernel.org/r/79ba410f-e0ef-2465-b94f-6b9a4a82adf5@i-love.sakura.ne.jp
[4] https://lkml.kernel.org/r/20200530011040.GB12419@rowland.harvard.edu
[5] https://lkml.kernel.org/r/c85331fc-874c-6e46-a77f-0ef1dc075308@i-love.sakura.ne.jp
Reported-by: syzbot <syzbot+854768b99f19e89d7f81@syzkaller.appspotmail.com>
Cc: stable <stable@vger.kernel.org>
Co-developed-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Oliver Neukum <oneukum@suse.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Link: https://lore.kernel.org/r/20200928141755.3476-1-penguin-kernel@I-love.SAKURA.ne.jp
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-09-28 07:17:55 -07:00
|
|
|
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);
|
2008-05-13 08:01:25 -07:00
|
|
|
|
USB: cdc-wdm: Make wdm_flush() interruptible and add wdm_fsync().
syzbot is reporting hung task at wdm_flush() [1], for there is a circular
dependency that wdm_flush() from flip_close() for /dev/cdc-wdm0 forever
waits for /dev/raw-gadget to be closed while close() for /dev/raw-gadget
cannot be called unless close() for /dev/cdc-wdm0 completes.
Tetsuo Handa considered that such circular dependency is an usage error [2]
which corresponds to an unresponding broken hardware [3]. But Alan Stern
responded that we should be prepared for such hardware [4]. Therefore,
this patch changes wdm_flush() to use wait_event_interruptible_timeout()
which gives up after 30 seconds, for hardware that remains silent must be
ignored. The 30 seconds are coming out of thin air.
Changing wait_event() to wait_event_interruptible_timeout() makes error
reporting from close() syscall less reliable. To compensate it, this patch
also implements wdm_fsync() which does not use timeout. Those who want to
be very sure that data has gone out to the device are now advised to call
fsync(), with a caveat that fsync() can return -EINVAL when running on
older kernels which do not implement wdm_fsync().
This patch also fixes three more problems (listed below) found during
exhaustive discussion and testing.
Since multiple threads can concurrently call wdm_write()/wdm_flush(),
we need to use wake_up_all() whenever clearing WDM_IN_USE in order to
make sure that all waiters are woken up. Also, error reporting needs
to use fetch-and-clear approach in order not to report same error for
multiple times.
Since wdm_flush() checks WDM_DISCONNECTING, wdm_write() should as well
check WDM_DISCONNECTING.
In wdm_flush(), since locks are not held, it is not safe to dereference
desc->intf after checking that WDM_DISCONNECTING is not set [5]. Thus,
remove dev_err() from wdm_flush().
[1] https://syzkaller.appspot.com/bug?id=e7b761593b23eb50855b9ea31e3be5472b711186
[2] https://lkml.kernel.org/r/27b7545e-8f41-10b8-7c02-e35a08eb1611@i-love.sakura.ne.jp
[3] https://lkml.kernel.org/r/79ba410f-e0ef-2465-b94f-6b9a4a82adf5@i-love.sakura.ne.jp
[4] https://lkml.kernel.org/r/20200530011040.GB12419@rowland.harvard.edu
[5] https://lkml.kernel.org/r/c85331fc-874c-6e46-a77f-0ef1dc075308@i-love.sakura.ne.jp
Reported-by: syzbot <syzbot+854768b99f19e89d7f81@syzkaller.appspotmail.com>
Cc: stable <stable@vger.kernel.org>
Co-developed-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Oliver Neukum <oneukum@suse.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Link: https://lore.kernel.org/r/20200928141755.3476-1-penguin-kernel@I-love.SAKURA.ne.jp
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-09-28 07:17:55 -07:00
|
|
|
/*
|
|
|
|
* To report the correct error. This is best effort.
|
|
|
|
* We are inevitably racing with the hardware.
|
|
|
|
*/
|
2019-08-27 03:34:36 -07:00
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags))
|
|
|
|
return -ENODEV;
|
USB: cdc-wdm: Make wdm_flush() interruptible and add wdm_fsync().
syzbot is reporting hung task at wdm_flush() [1], for there is a circular
dependency that wdm_flush() from flip_close() for /dev/cdc-wdm0 forever
waits for /dev/raw-gadget to be closed while close() for /dev/raw-gadget
cannot be called unless close() for /dev/cdc-wdm0 completes.
Tetsuo Handa considered that such circular dependency is an usage error [2]
which corresponds to an unresponding broken hardware [3]. But Alan Stern
responded that we should be prepared for such hardware [4]. Therefore,
this patch changes wdm_flush() to use wait_event_interruptible_timeout()
which gives up after 30 seconds, for hardware that remains silent must be
ignored. The 30 seconds are coming out of thin air.
Changing wait_event() to wait_event_interruptible_timeout() makes error
reporting from close() syscall less reliable. To compensate it, this patch
also implements wdm_fsync() which does not use timeout. Those who want to
be very sure that data has gone out to the device are now advised to call
fsync(), with a caveat that fsync() can return -EINVAL when running on
older kernels which do not implement wdm_fsync().
This patch also fixes three more problems (listed below) found during
exhaustive discussion and testing.
Since multiple threads can concurrently call wdm_write()/wdm_flush(),
we need to use wake_up_all() whenever clearing WDM_IN_USE in order to
make sure that all waiters are woken up. Also, error reporting needs
to use fetch-and-clear approach in order not to report same error for
multiple times.
Since wdm_flush() checks WDM_DISCONNECTING, wdm_write() should as well
check WDM_DISCONNECTING.
In wdm_flush(), since locks are not held, it is not safe to dereference
desc->intf after checking that WDM_DISCONNECTING is not set [5]. Thus,
remove dev_err() from wdm_flush().
[1] https://syzkaller.appspot.com/bug?id=e7b761593b23eb50855b9ea31e3be5472b711186
[2] https://lkml.kernel.org/r/27b7545e-8f41-10b8-7c02-e35a08eb1611@i-love.sakura.ne.jp
[3] https://lkml.kernel.org/r/79ba410f-e0ef-2465-b94f-6b9a4a82adf5@i-love.sakura.ne.jp
[4] https://lkml.kernel.org/r/20200530011040.GB12419@rowland.harvard.edu
[5] https://lkml.kernel.org/r/c85331fc-874c-6e46-a77f-0ef1dc075308@i-love.sakura.ne.jp
Reported-by: syzbot <syzbot+854768b99f19e89d7f81@syzkaller.appspotmail.com>
Cc: stable <stable@vger.kernel.org>
Co-developed-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Oliver Neukum <oneukum@suse.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Link: https://lore.kernel.org/r/20200928141755.3476-1-penguin-kernel@I-love.SAKURA.ne.jp
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-09-28 07:17:55 -07:00
|
|
|
if (!rv)
|
|
|
|
return -EIO;
|
|
|
|
if (rv < 0)
|
|
|
|
return -EINTR;
|
|
|
|
|
|
|
|
spin_lock_irq(&desc->iuspin);
|
|
|
|
rv = desc->werr;
|
|
|
|
desc->werr = 0;
|
|
|
|
spin_unlock_irq(&desc->iuspin);
|
|
|
|
|
|
|
|
return usb_translate_errors(rv);
|
|
|
|
|
|
|
|
}
|
2008-05-13 08:01:25 -07:00
|
|
|
|
USB: cdc-wdm: Make wdm_flush() interruptible and add wdm_fsync().
syzbot is reporting hung task at wdm_flush() [1], for there is a circular
dependency that wdm_flush() from flip_close() for /dev/cdc-wdm0 forever
waits for /dev/raw-gadget to be closed while close() for /dev/raw-gadget
cannot be called unless close() for /dev/cdc-wdm0 completes.
Tetsuo Handa considered that such circular dependency is an usage error [2]
which corresponds to an unresponding broken hardware [3]. But Alan Stern
responded that we should be prepared for such hardware [4]. Therefore,
this patch changes wdm_flush() to use wait_event_interruptible_timeout()
which gives up after 30 seconds, for hardware that remains silent must be
ignored. The 30 seconds are coming out of thin air.
Changing wait_event() to wait_event_interruptible_timeout() makes error
reporting from close() syscall less reliable. To compensate it, this patch
also implements wdm_fsync() which does not use timeout. Those who want to
be very sure that data has gone out to the device are now advised to call
fsync(), with a caveat that fsync() can return -EINVAL when running on
older kernels which do not implement wdm_fsync().
This patch also fixes three more problems (listed below) found during
exhaustive discussion and testing.
Since multiple threads can concurrently call wdm_write()/wdm_flush(),
we need to use wake_up_all() whenever clearing WDM_IN_USE in order to
make sure that all waiters are woken up. Also, error reporting needs
to use fetch-and-clear approach in order not to report same error for
multiple times.
Since wdm_flush() checks WDM_DISCONNECTING, wdm_write() should as well
check WDM_DISCONNECTING.
In wdm_flush(), since locks are not held, it is not safe to dereference
desc->intf after checking that WDM_DISCONNECTING is not set [5]. Thus,
remove dev_err() from wdm_flush().
[1] https://syzkaller.appspot.com/bug?id=e7b761593b23eb50855b9ea31e3be5472b711186
[2] https://lkml.kernel.org/r/27b7545e-8f41-10b8-7c02-e35a08eb1611@i-love.sakura.ne.jp
[3] https://lkml.kernel.org/r/79ba410f-e0ef-2465-b94f-6b9a4a82adf5@i-love.sakura.ne.jp
[4] https://lkml.kernel.org/r/20200530011040.GB12419@rowland.harvard.edu
[5] https://lkml.kernel.org/r/c85331fc-874c-6e46-a77f-0ef1dc075308@i-love.sakura.ne.jp
Reported-by: syzbot <syzbot+854768b99f19e89d7f81@syzkaller.appspotmail.com>
Cc: stable <stable@vger.kernel.org>
Co-developed-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Oliver Neukum <oneukum@suse.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Link: https://lore.kernel.org/r/20200928141755.3476-1-penguin-kernel@I-love.SAKURA.ne.jp
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-09-28 07:17:55 -07:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
static int 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.
|
|
|
|
*/
|
|
|
|
static int wdm_flush(struct file *file, fl_owner_t id)
|
|
|
|
{
|
|
|
|
return wdm_wait_for_response(file, WDM_FLUSH_TIMEOUT);
|
2008-05-13 08:01:25 -07:00
|
|
|
}
|
|
|
|
|
2017-07-03 03:39:46 -07:00
|
|
|
static __poll_t wdm_poll(struct file *file, struct poll_table_struct *wait)
|
2008-05-13 08:01:25 -07:00
|
|
|
{
|
|
|
|
struct wdm_device *desc = file->private_data;
|
|
|
|
unsigned long flags;
|
2017-07-03 03:39:46 -07:00
|
|
|
__poll_t mask = 0;
|
2008-05-13 08:01:25 -07:00
|
|
|
|
|
|
|
spin_lock_irqsave(&desc->iuspin, flags);
|
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
|
2018-02-11 15:34:03 -07:00
|
|
|
mask = EPOLLHUP | EPOLLERR;
|
2008-05-13 08:01:25 -07:00
|
|
|
spin_unlock_irqrestore(&desc->iuspin, flags);
|
|
|
|
goto desc_out;
|
|
|
|
}
|
|
|
|
if (test_bit(WDM_READ, &desc->flags))
|
2018-02-11 15:34:03 -07:00
|
|
|
mask = EPOLLIN | EPOLLRDNORM;
|
2008-05-13 08:01:25 -07:00
|
|
|
if (desc->rerr || desc->werr)
|
2018-02-11 15:34:03 -07:00
|
|
|
mask |= EPOLLERR;
|
2008-05-13 08:01:25 -07:00
|
|
|
if (!test_bit(WDM_IN_USE, &desc->flags))
|
2018-02-11 15:34:03 -07:00
|
|
|
mask |= EPOLLOUT | EPOLLWRNORM;
|
2008-05-13 08:01:25 -07:00
|
|
|
spin_unlock_irqrestore(&desc->iuspin, flags);
|
|
|
|
|
|
|
|
poll_wait(file, &desc->wait, wait);
|
|
|
|
|
|
|
|
desc_out:
|
|
|
|
return mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wdm_open(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
int minor = iminor(inode);
|
|
|
|
int rv = -ENODEV;
|
|
|
|
struct usb_interface *intf;
|
|
|
|
struct wdm_device *desc;
|
|
|
|
|
|
|
|
mutex_lock(&wdm_mutex);
|
2012-03-06 09:29:21 -07:00
|
|
|
desc = wdm_find_device_by_minor(minor);
|
|
|
|
if (!desc)
|
2008-05-13 08:01:25 -07:00
|
|
|
goto out;
|
|
|
|
|
2012-03-06 09:29:21 -07:00
|
|
|
intf = desc->intf;
|
2008-05-13 08:01:25 -07:00
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags))
|
|
|
|
goto out;
|
|
|
|
file->private_data = desc;
|
|
|
|
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
if (test_bit(WDM_WWAN_IN_USE, &desc->flags)) {
|
|
|
|
rv = -EBUSY;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2008-06-24 06:56:10 -07:00
|
|
|
rv = usb_autopm_get_interface(desc->intf);
|
2008-05-13 08:01:25 -07:00
|
|
|
if (rv < 0) {
|
2008-08-14 09:37:34 -07:00
|
|
|
dev_err(&desc->intf->dev, "Error autopm - %d\n", rv);
|
2008-05-13 08:01:25 -07:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2012-01-16 04:41:48 -07:00
|
|
|
/* using write lock to protect desc->count */
|
|
|
|
mutex_lock(&desc->wlock);
|
2008-06-24 06:56:10 -07:00
|
|
|
if (!desc->count++) {
|
2011-04-29 05:12:21 -07:00
|
|
|
desc->werr = 0;
|
|
|
|
desc->rerr = 0;
|
2008-06-24 06:56:10 -07:00
|
|
|
rv = usb_submit_urb(desc->validity, GFP_KERNEL);
|
|
|
|
if (rv < 0) {
|
|
|
|
desc->count--;
|
2008-08-14 09:37:34 -07:00
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"Error submitting int urb - %d\n", rv);
|
2012-04-30 00:57:31 -07:00
|
|
|
rv = usb_translate_errors(rv);
|
2008-06-24 06:56:10 -07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
rv = 0;
|
|
|
|
}
|
2012-01-16 04:41:48 -07:00
|
|
|
mutex_unlock(&desc->wlock);
|
2012-03-06 09:29:22 -07:00
|
|
|
if (desc->count == 1)
|
|
|
|
desc->manage_power(intf, 1);
|
2008-06-24 06:56:10 -07:00
|
|
|
usb_autopm_put_interface(desc->intf);
|
2008-05-13 08:01:25 -07:00
|
|
|
out:
|
|
|
|
mutex_unlock(&wdm_mutex);
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wdm_release(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc = file->private_data;
|
|
|
|
|
|
|
|
mutex_lock(&wdm_mutex);
|
2012-01-16 04:41:48 -07:00
|
|
|
|
|
|
|
/* using write lock to protect desc->count */
|
|
|
|
mutex_lock(&desc->wlock);
|
2008-05-13 08:01:25 -07:00
|
|
|
desc->count--;
|
2012-01-16 04:41:48 -07:00
|
|
|
mutex_unlock(&desc->wlock);
|
2008-06-24 06:56:10 -07:00
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
if (!desc->count) {
|
2012-04-30 00:26:11 -07:00
|
|
|
if (!test_bit(WDM_DISCONNECTING, &desc->flags)) {
|
2016-08-16 06:12:22 -07:00
|
|
|
dev_dbg(&desc->intf->dev, "wdm_release: cleanup\n");
|
2021-04-26 02:26:22 -07:00
|
|
|
poison_urbs(desc);
|
2013-10-29 10:29:10 -07:00
|
|
|
spin_lock_irq(&desc->iuspin);
|
|
|
|
desc->resp_count = 0;
|
2022-05-01 10:58:28 -07:00
|
|
|
clear_bit(WDM_RESPONDING, &desc->flags);
|
2013-10-29 10:29:10 -07:00
|
|
|
spin_unlock_irq(&desc->iuspin);
|
2012-03-06 09:29:22 -07:00
|
|
|
desc->manage_power(desc->intf, 0);
|
2021-04-26 02:26:22 -07:00
|
|
|
unpoison_urbs(desc);
|
2012-04-30 00:26:11 -07:00
|
|
|
} else {
|
2012-05-09 04:53:22 -07:00
|
|
|
/* must avoid dev_printk here as desc->intf is invalid */
|
|
|
|
pr_debug(KBUILD_MODNAME " %s: device gone - cleaning up\n", __func__);
|
2012-04-27 05:36:37 -07:00
|
|
|
cleanup(desc);
|
2012-04-30 00:26:11 -07:00
|
|
|
}
|
2008-05-13 08:01:25 -07:00
|
|
|
}
|
|
|
|
mutex_unlock(&wdm_mutex);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
USB: cdc-wdm: implement IOCTL_WDM_MAX_COMMAND
Userspace applications need to know the maximum supported message
size.
The cdc-wdm driver translates between a character device stream
and a message based protocol. Each message is transported as a
usb control message with no further encapsulation or syncronization.
Each read or write on the character device should translate to
exactly one usb control message to ensure that message boundaries
are kept intact. That means that the userspace application must
know the maximum message size supported by the device and driver,
making this size a vital part of the cdc-wdm character device API.
CDC WDM and CDC MBIM functions export the maximum supported
message size through CDC functional descriptors. The cdc-wdm and
cdc_mbim drivers will parse these descriptors and use the value
chosen by the device. The only current way for a userspace
application to retrive the value is by duplicating the descriptor
parsing. This is an unnecessary complex task, and application
writers are likely to postpone it, using a fixed value and adding
a "todo" item.
QMI functions have no way to tell the host what message size they
support. The qmi_wwan driver use a fixed value based on protocol
recommendations and observed device behaviour. Userspace
applications must know and hard code the same value. This scheme
will break if we ever encounter a QMI device needing a device
specific message size quirk. We are currently unable to support
such a device because using a non default size would break the
implicit userspace API.
The message size is currently a hidden attribute of the cdc-wdm
userspace API. Retrieving it is unnecessarily complex, increasing
the possibility of drivers and applications using different limits.
The resulting errors are hard to debug, and can only be replicated
on identical hardware.
Exporting the maximum message size from the driver simplifies the
task for the userspace application, and creates a unified
information source independent of device and function class. It also
serves to document that the message size is part of the cdc-wdm
userspace API.
This proposed API extension has been presented for the authors of
userspace applications and libraries using the current API: libmbim,
libqmi, uqmi, oFono and ModemManager. The replies were:
Aleksander Morgado:
"We do really need max message size for MBIM; and as you say, it may be
good to have the max message size info also for QMI, so the new ioctl
seems a good addition. So +1 from my side, for what it's worth."
Dan Williams:
"Yeah, +1 here. I'd prefer the sysfs file, but the fact that that
doesn't work for fd passing pretty much kills it."
No negative replies are so far received.
Cc: Aleksander Morgado <aleksander@lanedo.com>
Cc: Dan Williams <dcbw@redhat.com>
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Acked-by: Oliver Neukum <oliver@neukum.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2013-03-17 13:00:06 -07:00
|
|
|
static long wdm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc = file->private_data;
|
|
|
|
int rv = 0;
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case IOCTL_WDM_MAX_COMMAND:
|
|
|
|
if (copy_to_user((void __user *)arg, &desc->wMaxCommand, sizeof(desc->wMaxCommand)))
|
|
|
|
rv = -EFAULT;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rv = -ENOTTY;
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
static const struct file_operations wdm_fops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.read = wdm_read,
|
|
|
|
.write = wdm_write,
|
USB: cdc-wdm: Make wdm_flush() interruptible and add wdm_fsync().
syzbot is reporting hung task at wdm_flush() [1], for there is a circular
dependency that wdm_flush() from flip_close() for /dev/cdc-wdm0 forever
waits for /dev/raw-gadget to be closed while close() for /dev/raw-gadget
cannot be called unless close() for /dev/cdc-wdm0 completes.
Tetsuo Handa considered that such circular dependency is an usage error [2]
which corresponds to an unresponding broken hardware [3]. But Alan Stern
responded that we should be prepared for such hardware [4]. Therefore,
this patch changes wdm_flush() to use wait_event_interruptible_timeout()
which gives up after 30 seconds, for hardware that remains silent must be
ignored. The 30 seconds are coming out of thin air.
Changing wait_event() to wait_event_interruptible_timeout() makes error
reporting from close() syscall less reliable. To compensate it, this patch
also implements wdm_fsync() which does not use timeout. Those who want to
be very sure that data has gone out to the device are now advised to call
fsync(), with a caveat that fsync() can return -EINVAL when running on
older kernels which do not implement wdm_fsync().
This patch also fixes three more problems (listed below) found during
exhaustive discussion and testing.
Since multiple threads can concurrently call wdm_write()/wdm_flush(),
we need to use wake_up_all() whenever clearing WDM_IN_USE in order to
make sure that all waiters are woken up. Also, error reporting needs
to use fetch-and-clear approach in order not to report same error for
multiple times.
Since wdm_flush() checks WDM_DISCONNECTING, wdm_write() should as well
check WDM_DISCONNECTING.
In wdm_flush(), since locks are not held, it is not safe to dereference
desc->intf after checking that WDM_DISCONNECTING is not set [5]. Thus,
remove dev_err() from wdm_flush().
[1] https://syzkaller.appspot.com/bug?id=e7b761593b23eb50855b9ea31e3be5472b711186
[2] https://lkml.kernel.org/r/27b7545e-8f41-10b8-7c02-e35a08eb1611@i-love.sakura.ne.jp
[3] https://lkml.kernel.org/r/79ba410f-e0ef-2465-b94f-6b9a4a82adf5@i-love.sakura.ne.jp
[4] https://lkml.kernel.org/r/20200530011040.GB12419@rowland.harvard.edu
[5] https://lkml.kernel.org/r/c85331fc-874c-6e46-a77f-0ef1dc075308@i-love.sakura.ne.jp
Reported-by: syzbot <syzbot+854768b99f19e89d7f81@syzkaller.appspotmail.com>
Cc: stable <stable@vger.kernel.org>
Co-developed-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Signed-off-by: Oliver Neukum <oneukum@suse.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Link: https://lore.kernel.org/r/20200928141755.3476-1-penguin-kernel@I-love.SAKURA.ne.jp
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-09-28 07:17:55 -07:00
|
|
|
.fsync = wdm_fsync,
|
2008-05-13 08:01:25 -07:00
|
|
|
.open = wdm_open,
|
|
|
|
.flush = wdm_flush,
|
|
|
|
.release = wdm_release,
|
llseek: automatically add .llseek fop
All file_operations should get a .llseek operation so we can make
nonseekable_open the default for future file operations without a
.llseek pointer.
The three cases that we can automatically detect are no_llseek, seq_lseek
and default_llseek. For cases where we can we can automatically prove that
the file offset is always ignored, we use noop_llseek, which maintains
the current behavior of not returning an error from a seek.
New drivers should normally not use noop_llseek but instead use no_llseek
and call nonseekable_open at open time. Existing drivers can be converted
to do the same when the maintainer knows for certain that no user code
relies on calling seek on the device file.
The generated code is often incorrectly indented and right now contains
comments that clarify for each added line why a specific variant was
chosen. In the version that gets submitted upstream, the comments will
be gone and I will manually fix the indentation, because there does not
seem to be a way to do that using coccinelle.
Some amount of new code is currently sitting in linux-next that should get
the same modifications, which I will do at the end of the merge window.
Many thanks to Julia Lawall for helping me learn to write a semantic
patch that does all this.
===== begin semantic patch =====
// This adds an llseek= method to all file operations,
// as a preparation for making no_llseek the default.
//
// The rules are
// - use no_llseek explicitly if we do nonseekable_open
// - use seq_lseek for sequential files
// - use default_llseek if we know we access f_pos
// - use noop_llseek if we know we don't access f_pos,
// but we still want to allow users to call lseek
//
@ open1 exists @
identifier nested_open;
@@
nested_open(...)
{
<+...
nonseekable_open(...)
...+>
}
@ open exists@
identifier open_f;
identifier i, f;
identifier open1.nested_open;
@@
int open_f(struct inode *i, struct file *f)
{
<+...
(
nonseekable_open(...)
|
nested_open(...)
)
...+>
}
@ read disable optional_qualifier exists @
identifier read_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
expression E;
identifier func;
@@
ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off)
{
<+...
(
*off = E
|
*off += E
|
func(..., off, ...)
|
E = *off
)
...+>
}
@ read_no_fpos disable optional_qualifier exists @
identifier read_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
@@
ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off)
{
... when != off
}
@ write @
identifier write_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
expression E;
identifier func;
@@
ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off)
{
<+...
(
*off = E
|
*off += E
|
func(..., off, ...)
|
E = *off
)
...+>
}
@ write_no_fpos @
identifier write_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
@@
ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off)
{
... when != off
}
@ fops0 @
identifier fops;
@@
struct file_operations fops = {
...
};
@ has_llseek depends on fops0 @
identifier fops0.fops;
identifier llseek_f;
@@
struct file_operations fops = {
...
.llseek = llseek_f,
...
};
@ has_read depends on fops0 @
identifier fops0.fops;
identifier read_f;
@@
struct file_operations fops = {
...
.read = read_f,
...
};
@ has_write depends on fops0 @
identifier fops0.fops;
identifier write_f;
@@
struct file_operations fops = {
...
.write = write_f,
...
};
@ has_open depends on fops0 @
identifier fops0.fops;
identifier open_f;
@@
struct file_operations fops = {
...
.open = open_f,
...
};
// use no_llseek if we call nonseekable_open
////////////////////////////////////////////
@ nonseekable1 depends on !has_llseek && has_open @
identifier fops0.fops;
identifier nso ~= "nonseekable_open";
@@
struct file_operations fops = {
... .open = nso, ...
+.llseek = no_llseek, /* nonseekable */
};
@ nonseekable2 depends on !has_llseek @
identifier fops0.fops;
identifier open.open_f;
@@
struct file_operations fops = {
... .open = open_f, ...
+.llseek = no_llseek, /* open uses nonseekable */
};
// use seq_lseek for sequential files
/////////////////////////////////////
@ seq depends on !has_llseek @
identifier fops0.fops;
identifier sr ~= "seq_read";
@@
struct file_operations fops = {
... .read = sr, ...
+.llseek = seq_lseek, /* we have seq_read */
};
// use default_llseek if there is a readdir
///////////////////////////////////////////
@ fops1 depends on !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier readdir_e;
@@
// any other fop is used that changes pos
struct file_operations fops = {
... .readdir = readdir_e, ...
+.llseek = default_llseek, /* readdir is present */
};
// use default_llseek if at least one of read/write touches f_pos
/////////////////////////////////////////////////////////////////
@ fops2 depends on !fops1 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read.read_f;
@@
// read fops use offset
struct file_operations fops = {
... .read = read_f, ...
+.llseek = default_llseek, /* read accesses f_pos */
};
@ fops3 depends on !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier write.write_f;
@@
// write fops use offset
struct file_operations fops = {
... .write = write_f, ...
+ .llseek = default_llseek, /* write accesses f_pos */
};
// Use noop_llseek if neither read nor write accesses f_pos
///////////////////////////////////////////////////////////
@ fops4 depends on !fops1 && !fops2 && !fops3 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read_no_fpos.read_f;
identifier write_no_fpos.write_f;
@@
// write fops use offset
struct file_operations fops = {
...
.write = write_f,
.read = read_f,
...
+.llseek = noop_llseek, /* read and write both use no f_pos */
};
@ depends on has_write && !has_read && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier write_no_fpos.write_f;
@@
struct file_operations fops = {
... .write = write_f, ...
+.llseek = noop_llseek, /* write uses no f_pos */
};
@ depends on has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read_no_fpos.read_f;
@@
struct file_operations fops = {
... .read = read_f, ...
+.llseek = noop_llseek, /* read uses no f_pos */
};
@ depends on !has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
@@
struct file_operations fops = {
...
+.llseek = noop_llseek, /* no read or write fn */
};
===== End semantic patch =====
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Julia Lawall <julia@diku.dk>
Cc: Christoph Hellwig <hch@infradead.org>
2010-08-15 09:52:59 -07:00
|
|
|
.poll = wdm_poll,
|
USB: cdc-wdm: implement IOCTL_WDM_MAX_COMMAND
Userspace applications need to know the maximum supported message
size.
The cdc-wdm driver translates between a character device stream
and a message based protocol. Each message is transported as a
usb control message with no further encapsulation or syncronization.
Each read or write on the character device should translate to
exactly one usb control message to ensure that message boundaries
are kept intact. That means that the userspace application must
know the maximum message size supported by the device and driver,
making this size a vital part of the cdc-wdm character device API.
CDC WDM and CDC MBIM functions export the maximum supported
message size through CDC functional descriptors. The cdc-wdm and
cdc_mbim drivers will parse these descriptors and use the value
chosen by the device. The only current way for a userspace
application to retrive the value is by duplicating the descriptor
parsing. This is an unnecessary complex task, and application
writers are likely to postpone it, using a fixed value and adding
a "todo" item.
QMI functions have no way to tell the host what message size they
support. The qmi_wwan driver use a fixed value based on protocol
recommendations and observed device behaviour. Userspace
applications must know and hard code the same value. This scheme
will break if we ever encounter a QMI device needing a device
specific message size quirk. We are currently unable to support
such a device because using a non default size would break the
implicit userspace API.
The message size is currently a hidden attribute of the cdc-wdm
userspace API. Retrieving it is unnecessarily complex, increasing
the possibility of drivers and applications using different limits.
The resulting errors are hard to debug, and can only be replicated
on identical hardware.
Exporting the maximum message size from the driver simplifies the
task for the userspace application, and creates a unified
information source independent of device and function class. It also
serves to document that the message size is part of the cdc-wdm
userspace API.
This proposed API extension has been presented for the authors of
userspace applications and libraries using the current API: libmbim,
libqmi, uqmi, oFono and ModemManager. The replies were:
Aleksander Morgado:
"We do really need max message size for MBIM; and as you say, it may be
good to have the max message size info also for QMI, so the new ioctl
seems a good addition. So +1 from my side, for what it's worth."
Dan Williams:
"Yeah, +1 here. I'd prefer the sysfs file, but the fact that that
doesn't work for fd passing pretty much kills it."
No negative replies are so far received.
Cc: Aleksander Morgado <aleksander@lanedo.com>
Cc: Dan Williams <dcbw@redhat.com>
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Acked-by: Oliver Neukum <oliver@neukum.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2013-03-17 13:00:06 -07:00
|
|
|
.unlocked_ioctl = wdm_ioctl,
|
2018-09-11 12:59:08 -07:00
|
|
|
.compat_ioctl = compat_ptr_ioctl,
|
llseek: automatically add .llseek fop
All file_operations should get a .llseek operation so we can make
nonseekable_open the default for future file operations without a
.llseek pointer.
The three cases that we can automatically detect are no_llseek, seq_lseek
and default_llseek. For cases where we can we can automatically prove that
the file offset is always ignored, we use noop_llseek, which maintains
the current behavior of not returning an error from a seek.
New drivers should normally not use noop_llseek but instead use no_llseek
and call nonseekable_open at open time. Existing drivers can be converted
to do the same when the maintainer knows for certain that no user code
relies on calling seek on the device file.
The generated code is often incorrectly indented and right now contains
comments that clarify for each added line why a specific variant was
chosen. In the version that gets submitted upstream, the comments will
be gone and I will manually fix the indentation, because there does not
seem to be a way to do that using coccinelle.
Some amount of new code is currently sitting in linux-next that should get
the same modifications, which I will do at the end of the merge window.
Many thanks to Julia Lawall for helping me learn to write a semantic
patch that does all this.
===== begin semantic patch =====
// This adds an llseek= method to all file operations,
// as a preparation for making no_llseek the default.
//
// The rules are
// - use no_llseek explicitly if we do nonseekable_open
// - use seq_lseek for sequential files
// - use default_llseek if we know we access f_pos
// - use noop_llseek if we know we don't access f_pos,
// but we still want to allow users to call lseek
//
@ open1 exists @
identifier nested_open;
@@
nested_open(...)
{
<+...
nonseekable_open(...)
...+>
}
@ open exists@
identifier open_f;
identifier i, f;
identifier open1.nested_open;
@@
int open_f(struct inode *i, struct file *f)
{
<+...
(
nonseekable_open(...)
|
nested_open(...)
)
...+>
}
@ read disable optional_qualifier exists @
identifier read_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
expression E;
identifier func;
@@
ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off)
{
<+...
(
*off = E
|
*off += E
|
func(..., off, ...)
|
E = *off
)
...+>
}
@ read_no_fpos disable optional_qualifier exists @
identifier read_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
@@
ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off)
{
... when != off
}
@ write @
identifier write_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
expression E;
identifier func;
@@
ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off)
{
<+...
(
*off = E
|
*off += E
|
func(..., off, ...)
|
E = *off
)
...+>
}
@ write_no_fpos @
identifier write_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
@@
ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off)
{
... when != off
}
@ fops0 @
identifier fops;
@@
struct file_operations fops = {
...
};
@ has_llseek depends on fops0 @
identifier fops0.fops;
identifier llseek_f;
@@
struct file_operations fops = {
...
.llseek = llseek_f,
...
};
@ has_read depends on fops0 @
identifier fops0.fops;
identifier read_f;
@@
struct file_operations fops = {
...
.read = read_f,
...
};
@ has_write depends on fops0 @
identifier fops0.fops;
identifier write_f;
@@
struct file_operations fops = {
...
.write = write_f,
...
};
@ has_open depends on fops0 @
identifier fops0.fops;
identifier open_f;
@@
struct file_operations fops = {
...
.open = open_f,
...
};
// use no_llseek if we call nonseekable_open
////////////////////////////////////////////
@ nonseekable1 depends on !has_llseek && has_open @
identifier fops0.fops;
identifier nso ~= "nonseekable_open";
@@
struct file_operations fops = {
... .open = nso, ...
+.llseek = no_llseek, /* nonseekable */
};
@ nonseekable2 depends on !has_llseek @
identifier fops0.fops;
identifier open.open_f;
@@
struct file_operations fops = {
... .open = open_f, ...
+.llseek = no_llseek, /* open uses nonseekable */
};
// use seq_lseek for sequential files
/////////////////////////////////////
@ seq depends on !has_llseek @
identifier fops0.fops;
identifier sr ~= "seq_read";
@@
struct file_operations fops = {
... .read = sr, ...
+.llseek = seq_lseek, /* we have seq_read */
};
// use default_llseek if there is a readdir
///////////////////////////////////////////
@ fops1 depends on !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier readdir_e;
@@
// any other fop is used that changes pos
struct file_operations fops = {
... .readdir = readdir_e, ...
+.llseek = default_llseek, /* readdir is present */
};
// use default_llseek if at least one of read/write touches f_pos
/////////////////////////////////////////////////////////////////
@ fops2 depends on !fops1 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read.read_f;
@@
// read fops use offset
struct file_operations fops = {
... .read = read_f, ...
+.llseek = default_llseek, /* read accesses f_pos */
};
@ fops3 depends on !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier write.write_f;
@@
// write fops use offset
struct file_operations fops = {
... .write = write_f, ...
+ .llseek = default_llseek, /* write accesses f_pos */
};
// Use noop_llseek if neither read nor write accesses f_pos
///////////////////////////////////////////////////////////
@ fops4 depends on !fops1 && !fops2 && !fops3 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read_no_fpos.read_f;
identifier write_no_fpos.write_f;
@@
// write fops use offset
struct file_operations fops = {
...
.write = write_f,
.read = read_f,
...
+.llseek = noop_llseek, /* read and write both use no f_pos */
};
@ depends on has_write && !has_read && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier write_no_fpos.write_f;
@@
struct file_operations fops = {
... .write = write_f, ...
+.llseek = noop_llseek, /* write uses no f_pos */
};
@ depends on has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read_no_fpos.read_f;
@@
struct file_operations fops = {
... .read = read_f, ...
+.llseek = noop_llseek, /* read uses no f_pos */
};
@ depends on !has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
@@
struct file_operations fops = {
...
+.llseek = noop_llseek, /* no read or write fn */
};
===== End semantic patch =====
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Julia Lawall <julia@diku.dk>
Cc: Christoph Hellwig <hch@infradead.org>
2010-08-15 09:52:59 -07:00
|
|
|
.llseek = noop_llseek,
|
2008-05-13 08:01:25 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct usb_class_driver wdm_class = {
|
|
|
|
.name = "cdc-wdm%d",
|
|
|
|
.fops = &wdm_fops,
|
|
|
|
.minor_base = WDM_MINOR_BASE,
|
|
|
|
};
|
|
|
|
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
/* --- WWAN framework integration --- */
|
2021-09-29 12:45:46 -07:00
|
|
|
#ifdef CONFIG_WWAN
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
static int wdm_wwan_port_start(struct wwan_port *port)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc = wwan_port_get_drvdata(port);
|
|
|
|
|
|
|
|
/* 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 */
|
|
|
|
return usb_submit_urb(desc->validity, GFP_KERNEL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void wdm_wwan_port_stop(struct wwan_port *port)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc = wwan_port_get_drvdata(port);
|
|
|
|
|
|
|
|
/* Stop all transfers and disable WWAN mode */
|
2021-05-27 09:22:11 -07:00
|
|
|
poison_urbs(desc);
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
desc->manage_power(desc->intf, 0);
|
|
|
|
clear_bit(WDM_READ, &desc->flags);
|
|
|
|
clear_bit(WDM_WWAN_IN_USE, &desc->flags);
|
2021-05-27 09:22:11 -07:00
|
|
|
unpoison_urbs(desc);
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void wdm_wwan_port_tx_complete(struct urb *urb)
|
|
|
|
{
|
|
|
|
struct sk_buff *skb = urb->context;
|
|
|
|
struct wdm_device *desc = skb_shinfo(skb)->destructor_arg;
|
|
|
|
|
|
|
|
usb_autopm_put_interface(desc->intf);
|
|
|
|
wwan_port_txon(desc->wwanp);
|
|
|
|
kfree_skb(skb);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wdm_wwan_port_tx(struct wwan_port *port, struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc = wwan_port_get_drvdata(port);
|
|
|
|
struct usb_interface *intf = desc->intf;
|
|
|
|
struct usb_ctrlrequest *req = desc->orq;
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
rv = usb_autopm_get_interface(intf);
|
|
|
|
if (rv)
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
usb_fill_control_urb(
|
|
|
|
desc->command,
|
|
|
|
interface_to_usbdev(intf),
|
|
|
|
usb_sndctrlpipe(interface_to_usbdev(intf), 0),
|
|
|
|
(unsigned char *)req,
|
|
|
|
skb->data,
|
|
|
|
skb->len,
|
|
|
|
wdm_wwan_port_tx_complete,
|
|
|
|
skb
|
|
|
|
);
|
|
|
|
|
|
|
|
req->bRequestType = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE);
|
|
|
|
req->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND;
|
|
|
|
req->wValue = 0;
|
|
|
|
req->wIndex = desc->inum;
|
|
|
|
req->wLength = cpu_to_le16(skb->len);
|
|
|
|
|
|
|
|
skb_shinfo(skb)->destructor_arg = desc;
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2021-09-29 12:45:47 -07:00
|
|
|
static const struct wwan_port_ops wdm_wwan_port_ops = {
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
.start = wdm_wwan_port_start,
|
|
|
|
.stop = wdm_wwan_port_stop,
|
|
|
|
.tx = wdm_wwan_port_tx,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void wdm_wwan_init(struct wdm_device *desc)
|
|
|
|
{
|
|
|
|
struct usb_interface *intf = desc->intf;
|
|
|
|
struct wwan_port *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;
|
|
|
|
}
|
|
|
|
|
2023-03-16 02:58:20 -07:00
|
|
|
port = wwan_create_port(&intf->dev, desc->wwanp_type, &wdm_wwan_port_ops,
|
|
|
|
NULL, desc);
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
if (IS_ERR(port)) {
|
|
|
|
dev_err(&intf->dev, "%s: Unable to create WWAN port\n",
|
|
|
|
dev_name(intf->usb_dev));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
desc->wwanp = port;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void wdm_wwan_deinit(struct wdm_device *desc)
|
|
|
|
{
|
|
|
|
if (!desc->wwanp)
|
|
|
|
return;
|
|
|
|
|
|
|
|
wwan_remove_port(desc->wwanp);
|
|
|
|
desc->wwanp = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void wdm_wwan_rx(struct wdm_device *desc, int length)
|
|
|
|
{
|
|
|
|
struct wwan_port *port = desc->wwanp;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
|
|
|
|
/* Forward data to WWAN port */
|
|
|
|
skb = alloc_skb(length, GFP_ATOMIC);
|
|
|
|
if (!skb)
|
|
|
|
return;
|
|
|
|
|
2022-09-26 19:43:44 -07:00
|
|
|
skb_put_data(skb, desc->inbuf, length);
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
wwan_port_rx(port, skb);
|
|
|
|
|
|
|
|
/* inbuf has been copied, it is safe to check for outstanding data */
|
|
|
|
schedule_work(&desc->service_outs_intr);
|
|
|
|
}
|
2021-09-29 12:45:46 -07:00
|
|
|
#else /* CONFIG_WWAN */
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
static void wdm_wwan_init(struct wdm_device *desc) {}
|
|
|
|
static void wdm_wwan_deinit(struct wdm_device *desc) {}
|
|
|
|
static void wdm_wwan_rx(struct wdm_device *desc, int length) {}
|
2021-09-29 12:45:46 -07:00
|
|
|
#endif /* CONFIG_WWAN */
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
/* --- error handling --- */
|
|
|
|
static void wdm_rxwork(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc = container_of(work, struct wdm_device, rxwork);
|
|
|
|
unsigned long flags;
|
2013-08-06 05:22:59 -07:00
|
|
|
int rv = 0;
|
|
|
|
int responding;
|
2008-05-13 08:01:25 -07:00
|
|
|
|
|
|
|
spin_lock_irqsave(&desc->iuspin, flags);
|
|
|
|
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
|
|
|
|
spin_unlock_irqrestore(&desc->iuspin, flags);
|
|
|
|
} else {
|
2013-08-06 05:22:59 -07:00
|
|
|
responding = test_and_set_bit(WDM_RESPONDING, &desc->flags);
|
2008-05-13 08:01:25 -07:00
|
|
|
spin_unlock_irqrestore(&desc->iuspin, flags);
|
2013-08-06 05:22:59 -07:00
|
|
|
if (!responding)
|
|
|
|
rv = usb_submit_urb(desc->response, GFP_KERNEL);
|
2008-05-13 08:01:25 -07:00
|
|
|
if (rv < 0 && rv != -EPERM) {
|
|
|
|
spin_lock_irqsave(&desc->iuspin, flags);
|
2013-08-06 05:22:59 -07:00
|
|
|
clear_bit(WDM_RESPONDING, &desc->flags);
|
2008-05-13 08:01:25 -07:00
|
|
|
if (!test_bit(WDM_DISCONNECTING, &desc->flags))
|
|
|
|
schedule_work(&desc->rxwork);
|
|
|
|
spin_unlock_irqrestore(&desc->iuspin, flags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-14 09:36:46 -07:00
|
|
|
static void service_interrupt_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc;
|
|
|
|
|
|
|
|
desc = container_of(work, struct wdm_device, service_outs_intr);
|
|
|
|
|
|
|
|
spin_lock_irq(&desc->iuspin);
|
|
|
|
service_outstanding_interrupt(desc);
|
|
|
|
if (!desc->resp_count) {
|
|
|
|
set_bit(WDM_READ, &desc->flags);
|
|
|
|
wake_up(&desc->wait);
|
|
|
|
}
|
|
|
|
spin_unlock_irq(&desc->iuspin);
|
|
|
|
}
|
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
/* --- hotplug --- */
|
|
|
|
|
2012-03-06 09:29:22 -07:00
|
|
|
static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep,
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
u16 bufsize, enum wwan_port_type type,
|
|
|
|
int (*manage_power)(struct usb_interface *, int))
|
2008-05-13 08:01:25 -07:00
|
|
|
{
|
2012-03-06 09:29:20 -07:00
|
|
|
int rv = -ENOMEM;
|
2008-05-13 08:01:25 -07:00
|
|
|
struct wdm_device *desc;
|
|
|
|
|
|
|
|
desc = kzalloc(sizeof(struct wdm_device), GFP_KERNEL);
|
|
|
|
if (!desc)
|
|
|
|
goto out;
|
2012-03-06 09:29:21 -07:00
|
|
|
INIT_LIST_HEAD(&desc->device_list);
|
2012-01-16 04:41:48 -07:00
|
|
|
mutex_init(&desc->rlock);
|
|
|
|
mutex_init(&desc->wlock);
|
2008-05-13 08:01:25 -07:00
|
|
|
spin_lock_init(&desc->iuspin);
|
|
|
|
init_waitqueue_head(&desc->wait);
|
2012-03-06 09:29:20 -07:00
|
|
|
desc->wMaxCommand = bufsize;
|
2009-04-20 08:24:49 -07:00
|
|
|
/* this will be expanded and needed in hardware endianness */
|
2008-05-13 08:01:25 -07:00
|
|
|
desc->inum = cpu_to_le16((u16)intf->cur_altsetting->desc.bInterfaceNumber);
|
|
|
|
desc->intf = intf;
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
desc->wwanp_type = type;
|
2008-05-13 08:01:25 -07:00
|
|
|
INIT_WORK(&desc->rxwork, wdm_rxwork);
|
2018-06-14 09:36:46 -07:00
|
|
|
INIT_WORK(&desc->service_outs_intr, service_interrupt_work);
|
2008-05-13 08:01:25 -07:00
|
|
|
|
2021-06-21 06:24:15 -07:00
|
|
|
if (!usb_endpoint_is_int_in(ep)) {
|
|
|
|
rv = -EINVAL;
|
2008-05-13 08:01:25 -07:00
|
|
|
goto err;
|
2021-06-21 06:24:15 -07:00
|
|
|
}
|
2008-05-13 08:01:25 -07:00
|
|
|
|
2011-08-23 03:12:03 -07:00
|
|
|
desc->wMaxPacketSize = usb_endpoint_maxp(ep);
|
2008-05-13 08:01:25 -07:00
|
|
|
|
|
|
|
desc->orq = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
|
|
|
|
if (!desc->orq)
|
|
|
|
goto err;
|
|
|
|
desc->irq = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
|
|
|
|
if (!desc->irq)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
desc->validity = usb_alloc_urb(0, GFP_KERNEL);
|
|
|
|
if (!desc->validity)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
desc->response = usb_alloc_urb(0, GFP_KERNEL);
|
|
|
|
if (!desc->response)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
desc->command = usb_alloc_urb(0, GFP_KERNEL);
|
|
|
|
if (!desc->command)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
desc->ubuf = kmalloc(desc->wMaxCommand, GFP_KERNEL);
|
|
|
|
if (!desc->ubuf)
|
|
|
|
goto err;
|
|
|
|
|
2012-01-16 07:12:00 -07:00
|
|
|
desc->sbuf = kmalloc(desc->wMaxPacketSize, GFP_KERNEL);
|
2008-05-13 08:01:25 -07:00
|
|
|
if (!desc->sbuf)
|
|
|
|
goto err;
|
|
|
|
|
2012-01-16 07:12:00 -07:00
|
|
|
desc->inbuf = kmalloc(desc->wMaxCommand, GFP_KERNEL);
|
2008-05-13 08:01:25 -07:00
|
|
|
if (!desc->inbuf)
|
2012-01-16 07:12:00 -07:00
|
|
|
goto err;
|
2008-05-13 08:01:25 -07:00
|
|
|
|
|
|
|
usb_fill_int_urb(
|
|
|
|
desc->validity,
|
|
|
|
interface_to_usbdev(intf),
|
|
|
|
usb_rcvintpipe(interface_to_usbdev(intf), ep->bEndpointAddress),
|
|
|
|
desc->sbuf,
|
|
|
|
desc->wMaxPacketSize,
|
|
|
|
wdm_int_callback,
|
|
|
|
desc,
|
|
|
|
ep->bInterval
|
|
|
|
);
|
|
|
|
|
2012-01-16 07:11:58 -07:00
|
|
|
desc->irq->bRequestType = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE);
|
|
|
|
desc->irq->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
|
|
|
|
desc->irq->wValue = 0;
|
2015-03-20 06:29:34 -07:00
|
|
|
desc->irq->wIndex = desc->inum; /* already converted */
|
2012-01-16 07:11:58 -07:00
|
|
|
desc->irq->wLength = cpu_to_le16(desc->wMaxCommand);
|
|
|
|
|
|
|
|
usb_fill_control_urb(
|
|
|
|
desc->response,
|
2012-01-16 07:12:01 -07:00
|
|
|
interface_to_usbdev(intf),
|
2012-01-16 07:11:58 -07:00
|
|
|
/* using common endpoint 0 */
|
|
|
|
usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0),
|
|
|
|
(unsigned char *)desc->irq,
|
|
|
|
desc->inbuf,
|
|
|
|
desc->wMaxCommand,
|
|
|
|
wdm_in_callback,
|
|
|
|
desc
|
|
|
|
);
|
|
|
|
|
2012-03-06 09:29:22 -07:00
|
|
|
desc->manage_power = manage_power;
|
|
|
|
|
2012-03-06 09:29:21 -07:00
|
|
|
spin_lock(&wdm_device_list_lock);
|
|
|
|
list_add(&desc->device_list, &wdm_device_list);
|
|
|
|
spin_unlock(&wdm_device_list_lock);
|
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
rv = usb_register_dev(intf, &wdm_class);
|
|
|
|
if (rv < 0)
|
2012-03-06 09:29:21 -07:00
|
|
|
goto err;
|
2009-04-20 08:24:49 -07:00
|
|
|
else
|
2012-01-19 20:17:25 -07:00
|
|
|
dev_info(&intf->dev, "%s: USB WDM device\n", dev_name(intf->usb_dev));
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
|
|
|
|
wdm_wwan_init(desc);
|
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
out:
|
|
|
|
return rv;
|
|
|
|
err:
|
2012-05-09 04:53:23 -07:00
|
|
|
spin_lock(&wdm_device_list_lock);
|
|
|
|
list_del(&desc->device_list);
|
|
|
|
spin_unlock(&wdm_device_list_lock);
|
2012-03-06 09:29:20 -07:00
|
|
|
cleanup(desc);
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2012-03-06 09:29:22 -07:00
|
|
|
static int 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;
|
2013-11-29 12:17:45 -07:00
|
|
|
if (!rv)
|
|
|
|
usb_autopm_put_interface(intf);
|
|
|
|
return 0;
|
2012-03-06 09:29:22 -07:00
|
|
|
}
|
|
|
|
|
2012-03-06 09:29:20 -07:00
|
|
|
static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id)
|
|
|
|
{
|
|
|
|
int rv = -EINVAL;
|
|
|
|
struct usb_host_interface *iface;
|
|
|
|
struct usb_endpoint_descriptor *ep;
|
2016-07-14 06:41:33 -07:00
|
|
|
struct usb_cdc_parsed_header hdr;
|
2012-03-06 09:29:20 -07:00
|
|
|
u8 *buffer = intf->altsetting->extra;
|
|
|
|
int buflen = intf->altsetting->extralen;
|
|
|
|
u16 maxcom = WDM_DEFAULT_BUFSIZE;
|
|
|
|
|
|
|
|
if (!buffer)
|
|
|
|
goto err;
|
|
|
|
|
2016-07-14 06:41:33 -07:00
|
|
|
cdc_parse_cdc_header(&hdr, intf, buffer, buflen);
|
|
|
|
|
|
|
|
if (hdr.usb_cdc_dmm_desc)
|
|
|
|
maxcom = le16_to_cpu(hdr.usb_cdc_dmm_desc->wMaxCommand);
|
2012-03-06 09:29:20 -07:00
|
|
|
|
|
|
|
iface = intf->cur_altsetting;
|
|
|
|
if (iface->desc.bNumEndpoints != 1)
|
|
|
|
goto err;
|
|
|
|
ep = &iface->endpoint[0].desc;
|
|
|
|
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
rv = wdm_create(intf, ep, maxcom, WWAN_PORT_UNKNOWN, &wdm_manage_power);
|
2012-03-06 09:29:20 -07:00
|
|
|
|
|
|
|
err:
|
2008-05-13 08:01:25 -07:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2012-03-06 09:29:22 -07:00
|
|
|
/**
|
|
|
|
* 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
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
* @type: Type/protocol of the transported data (MBIM, QMI...)
|
2020-07-15 02:32:06 -07:00
|
|
|
* @manage_power: call-back invoked during open and release to
|
|
|
|
* manage the device's power
|
2012-03-06 09:29:22 -07:00
|
|
|
* 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,
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
int bufsize, enum wwan_port_type type,
|
2012-03-06 09:29:22 -07:00
|
|
|
int (*manage_power)(struct usb_interface *, int))
|
|
|
|
{
|
2019-05-31 06:53:47 -07:00
|
|
|
int rv;
|
2012-03-06 09:29:22 -07:00
|
|
|
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
rv = wdm_create(intf, ep, bufsize, type, manage_power);
|
2012-03-06 09:29:22 -07:00
|
|
|
if (rv < 0)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
return &wdm_driver;
|
|
|
|
err:
|
|
|
|
return ERR_PTR(rv);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(usb_cdc_wdm_register);
|
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
static void wdm_disconnect(struct usb_interface *intf)
|
|
|
|
{
|
|
|
|
struct wdm_device *desc;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
usb_deregister_dev(intf, &wdm_class);
|
2012-03-06 09:29:21 -07:00
|
|
|
desc = wdm_find_device(intf);
|
2008-05-13 08:01:25 -07:00
|
|
|
mutex_lock(&wdm_mutex);
|
|
|
|
|
usb: class: cdc-wdm: WWAN framework integration
The WWAN framework provides a unified way to handle WWAN/modems and its
control port(s). It has initially been introduced to support MHI/PCI
modems, offering the same control protocols as the USB variants such as
MBIM, QMI, AT... The WWAN framework exposes these control protocols as
character devices, similarly to cdc-wdm, but in a bus agnostic fashion.
This change adds registration of the USB modem cdc-wdm control endpoints
to the WWAN framework as standard control ports (wwanXpY...).
Exposing cdc-wdm through WWAN framework normally maintains backward
compatibility, e.g:
$ qmicli --device-open-qmi -d /dev/wwan0p1QMI --dms-get-ids
instead of
$ qmicli --device-open-qmi -d /dev/cdc-wdm0 --dms-get-ids
However, some tools may rely on cdc-wdm driver/device name for device
detection. It is then safer to keep the 'legacy' cdc-wdm character
device to prevent any breakage. This is handled in this change by
API mutual exclusion, only one access method can be used at a time,
either cdc-wdm chardev or WWAN API.
Note that unknown channel types (other than MBIM, AT or MBIM) are not
registered to the WWAN framework.
Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-05-11 07:42:23 -07:00
|
|
|
wdm_wwan_deinit(desc);
|
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
/* 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);
|
2012-01-16 07:11:57 -07:00
|
|
|
wake_up_all(&desc->wait);
|
2012-01-16 04:41:48 -07:00
|
|
|
mutex_lock(&desc->rlock);
|
|
|
|
mutex_lock(&desc->wlock);
|
2021-04-26 02:26:22 -07:00
|
|
|
poison_urbs(desc);
|
2010-02-27 12:56:47 -07:00
|
|
|
cancel_work_sync(&desc->rxwork);
|
2018-06-14 09:36:46 -07:00
|
|
|
cancel_work_sync(&desc->service_outs_intr);
|
2012-01-16 04:41:48 -07:00
|
|
|
mutex_unlock(&desc->wlock);
|
|
|
|
mutex_unlock(&desc->rlock);
|
2012-05-09 04:53:23 -07:00
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
if (!desc->count)
|
|
|
|
cleanup(desc);
|
2012-04-30 00:26:11 -07:00
|
|
|
else
|
2016-08-15 02:17:55 -07:00
|
|
|
dev_dbg(&intf->dev, "%d open files - postponing cleanup\n", desc->count);
|
2008-05-13 08:01:25 -07:00
|
|
|
mutex_unlock(&wdm_mutex);
|
|
|
|
}
|
|
|
|
|
2010-02-27 12:56:47 -07:00
|
|
|
#ifdef CONFIG_PM
|
2008-06-24 06:56:10 -07:00
|
|
|
static int wdm_suspend(struct usb_interface *intf, pm_message_t message)
|
|
|
|
{
|
2012-03-06 09:29:21 -07:00
|
|
|
struct wdm_device *desc = wdm_find_device(intf);
|
2008-06-24 06:56:10 -07:00
|
|
|
int rv = 0;
|
|
|
|
|
|
|
|
dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor);
|
|
|
|
|
2010-02-27 12:56:47 -07:00
|
|
|
/* if this is an autosuspend the caller does the locking */
|
2012-01-16 04:41:48 -07:00
|
|
|
if (!PMSG_IS_AUTO(message)) {
|
|
|
|
mutex_lock(&desc->rlock);
|
|
|
|
mutex_lock(&desc->wlock);
|
|
|
|
}
|
2010-02-27 12:56:22 -07:00
|
|
|
spin_lock_irq(&desc->iuspin);
|
2010-02-27 12:56:47 -07:00
|
|
|
|
2011-08-19 14:49:48 -07:00
|
|
|
if (PMSG_IS_AUTO(message) &&
|
2010-02-27 12:54:59 -07:00
|
|
|
(test_bit(WDM_IN_USE, &desc->flags)
|
|
|
|
|| test_bit(WDM_RESPONDING, &desc->flags))) {
|
2010-02-27 12:56:22 -07:00
|
|
|
spin_unlock_irq(&desc->iuspin);
|
2008-06-24 06:56:10 -07:00
|
|
|
rv = -EBUSY;
|
|
|
|
} else {
|
2010-02-27 12:56:47 -07:00
|
|
|
|
2010-02-27 12:55:52 -07:00
|
|
|
set_bit(WDM_SUSPENDING, &desc->flags);
|
2010-02-27 12:56:22 -07:00
|
|
|
spin_unlock_irq(&desc->iuspin);
|
2010-02-27 12:56:47 -07:00
|
|
|
/* callback submits work - order is essential */
|
2021-04-26 02:26:22 -07:00
|
|
|
poison_urbs(desc);
|
2010-02-27 12:56:47 -07:00
|
|
|
cancel_work_sync(&desc->rxwork);
|
2018-06-14 09:36:46 -07:00
|
|
|
cancel_work_sync(&desc->service_outs_intr);
|
2021-04-26 02:26:22 -07:00
|
|
|
unpoison_urbs(desc);
|
2008-06-24 06:56:10 -07:00
|
|
|
}
|
2012-01-16 04:41:48 -07:00
|
|
|
if (!PMSG_IS_AUTO(message)) {
|
|
|
|
mutex_unlock(&desc->wlock);
|
|
|
|
mutex_unlock(&desc->rlock);
|
|
|
|
}
|
2008-06-24 06:56:10 -07:00
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
2010-02-27 12:56:47 -07:00
|
|
|
#endif
|
2008-06-24 06:56:10 -07:00
|
|
|
|
|
|
|
static int recover_from_urb_loss(struct wdm_device *desc)
|
|
|
|
{
|
|
|
|
int rv = 0;
|
|
|
|
|
|
|
|
if (desc->count) {
|
|
|
|
rv = usb_submit_urb(desc->validity, GFP_NOIO);
|
|
|
|
if (rv < 0)
|
2008-08-14 09:37:34 -07:00
|
|
|
dev_err(&desc->intf->dev,
|
|
|
|
"Error resume submitting int urb - %d\n", rv);
|
2008-06-24 06:56:10 -07:00
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
2010-02-27 12:56:47 -07:00
|
|
|
|
|
|
|
#ifdef CONFIG_PM
|
2008-06-24 06:56:10 -07:00
|
|
|
static int wdm_resume(struct usb_interface *intf)
|
|
|
|
{
|
2012-03-06 09:29:21 -07:00
|
|
|
struct wdm_device *desc = wdm_find_device(intf);
|
2008-06-24 06:56:10 -07:00
|
|
|
int rv;
|
|
|
|
|
|
|
|
dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor);
|
2010-02-27 12:57:12 -07:00
|
|
|
|
2010-02-27 12:55:52 -07:00
|
|
|
clear_bit(WDM_SUSPENDING, &desc->flags);
|
2010-02-27 12:56:22 -07:00
|
|
|
rv = recover_from_urb_loss(desc);
|
2010-02-27 12:57:12 -07:00
|
|
|
|
2008-06-24 06:56:10 -07:00
|
|
|
return rv;
|
|
|
|
}
|
2010-02-27 12:56:47 -07:00
|
|
|
#endif
|
2008-06-24 06:56:10 -07:00
|
|
|
|
|
|
|
static int wdm_pre_reset(struct usb_interface *intf)
|
|
|
|
{
|
2012-03-06 09:29:21 -07:00
|
|
|
struct wdm_device *desc = wdm_find_device(intf);
|
2008-06-24 06:56:10 -07:00
|
|
|
|
2011-04-29 05:12:21 -07:00
|
|
|
/*
|
|
|
|
* we notify everybody using poll of
|
|
|
|
* an exceptional situation
|
|
|
|
* must be done before recovery lest a spontaneous
|
|
|
|
* message from the device is lost
|
|
|
|
*/
|
|
|
|
spin_lock_irq(&desc->iuspin);
|
2012-02-10 01:44:08 -07:00
|
|
|
set_bit(WDM_RESETTING, &desc->flags); /* inform read/write */
|
|
|
|
set_bit(WDM_READ, &desc->flags); /* unblock read */
|
|
|
|
clear_bit(WDM_IN_USE, &desc->flags); /* unblock write */
|
2011-04-29 05:12:21 -07:00
|
|
|
desc->rerr = -EINTR;
|
|
|
|
spin_unlock_irq(&desc->iuspin);
|
|
|
|
wake_up_all(&desc->wait);
|
2012-02-10 01:44:08 -07:00
|
|
|
mutex_lock(&desc->rlock);
|
|
|
|
mutex_lock(&desc->wlock);
|
2021-04-26 02:26:22 -07:00
|
|
|
poison_urbs(desc);
|
2012-02-10 01:44:08 -07:00
|
|
|
cancel_work_sync(&desc->rxwork);
|
2018-06-14 09:36:46 -07:00
|
|
|
cancel_work_sync(&desc->service_outs_intr);
|
2008-06-24 06:56:10 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wdm_post_reset(struct usb_interface *intf)
|
|
|
|
{
|
2012-03-06 09:29:21 -07:00
|
|
|
struct wdm_device *desc = wdm_find_device(intf);
|
2008-06-24 06:56:10 -07:00
|
|
|
int rv;
|
|
|
|
|
2021-04-26 02:26:22 -07:00
|
|
|
unpoison_urbs(desc);
|
2013-03-12 06:52:42 -07:00
|
|
|
clear_bit(WDM_OVERFLOW, &desc->flags);
|
2012-02-10 01:44:08 -07:00
|
|
|
clear_bit(WDM_RESETTING, &desc->flags);
|
2008-06-24 06:56:10 -07:00
|
|
|
rv = recover_from_urb_loss(desc);
|
2012-01-16 04:41:48 -07:00
|
|
|
mutex_unlock(&desc->wlock);
|
|
|
|
mutex_unlock(&desc->rlock);
|
2019-02-18 07:34:51 -07:00
|
|
|
return rv;
|
2008-06-24 06:56:10 -07:00
|
|
|
}
|
|
|
|
|
2008-05-13 08:01:25 -07:00
|
|
|
static struct usb_driver wdm_driver = {
|
|
|
|
.name = "cdc_wdm",
|
|
|
|
.probe = wdm_probe,
|
|
|
|
.disconnect = wdm_disconnect,
|
2010-02-27 12:56:47 -07:00
|
|
|
#ifdef CONFIG_PM
|
2008-06-24 06:56:10 -07:00
|
|
|
.suspend = wdm_suspend,
|
|
|
|
.resume = wdm_resume,
|
|
|
|
.reset_resume = wdm_resume,
|
2010-02-27 12:56:47 -07:00
|
|
|
#endif
|
2008-06-24 06:56:10 -07:00
|
|
|
.pre_reset = wdm_pre_reset,
|
|
|
|
.post_reset = wdm_post_reset,
|
2008-05-13 08:01:25 -07:00
|
|
|
.id_table = wdm_ids,
|
2008-06-24 06:56:10 -07:00
|
|
|
.supports_autosuspend = 1,
|
2012-04-23 10:08:51 -07:00
|
|
|
.disable_hub_initiated_lpm = 1,
|
2008-05-13 08:01:25 -07:00
|
|
|
};
|
|
|
|
|
2011-11-18 10:34:02 -07:00
|
|
|
module_usb_driver(wdm_driver);
|
2008-05-13 08:01:25 -07:00
|
|
|
|
|
|
|
MODULE_AUTHOR(DRIVER_AUTHOR);
|
2008-06-19 05:20:18 -07:00
|
|
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
2008-05-13 08:01:25 -07:00
|
|
|
MODULE_LICENSE("GPL");
|