summaryrefslogtreecommitdiff
path: root/drivers/net/wwan
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wwan')
-rw-r--r--drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c5
-rw-r--r--drivers/net/wwan/mhi_wwan_ctrl.c262
-rw-r--r--drivers/net/wwan/wwan_core.c172
-rw-r--r--drivers/net/wwan/wwan_hwsim.c4
4 files changed, 385 insertions, 58 deletions
diff --git a/drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c b/drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c
index f2f86f41fd10..004920068c45 100644
--- a/drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c
+++ b/drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c
@@ -14,6 +14,11 @@ ipc_protocol_free_msg_get(struct iosm_protocol *ipc_protocol, int *index)
u32 new_head = (head + 1) % IPC_MEM_MSG_ENTRIES;
union ipc_mem_msg_entry *msg;
+ if (head >= IPC_MEM_MSG_ENTRIES) {
+ dev_err(ipc_protocol->dev, "invalid message head %u", head);
+ return NULL;
+ }
+
if (new_head == le32_to_cpu(ipc_protocol->p_ap_shm->msg_tail)) {
dev_err(ipc_protocol->dev, "message ring is full");
return NULL;
diff --git a/drivers/net/wwan/mhi_wwan_ctrl.c b/drivers/net/wwan/mhi_wwan_ctrl.c
index a31d8540fbb8..e3c207588956 100644
--- a/drivers/net/wwan/mhi_wwan_ctrl.c
+++ b/drivers/net/wwan/mhi_wwan_ctrl.c
@@ -1,8 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2021, Linaro Ltd <loic.poulain@linaro.org> */
#include <linux/kernel.h>
+#include <linux/list.h>
#include <linux/mhi.h>
#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/termios.h>
+#include <linux/workqueue.h>
#include <linux/wwan.h>
/* MHI wwan flags */
@@ -14,6 +19,32 @@ enum mhi_wwan_flags {
#define MHI_WWAN_MAX_MTU 0x8000
+/* IP_CTRL channel message that sets the modem's DTR/RTS control lines */
+struct mhi_dtr_ctrl_msg {
+ __le32 preamble;
+ __le32 msg_id;
+ __le32 dest_id;
+ __le32 size;
+ __le32 msg;
+} __packed;
+
+#define MHI_DTR_CTRL_MAGIC 0x4C525443 /* 'CTRL' */
+#define MHI_DTR_MSG_DTR BIT(0)
+#define MHI_DTR_MSG_RTS BIT(1)
+#define MHI_DTR_HOST_STATE 0x10
+
+/* Per-controller IP_CTRL channel, used to signal DTR/RTS to that modem */
+struct mhi_wwan_dtr {
+ struct mhi_controller *cntrl;
+ struct mhi_device *mhi_dev;
+ struct list_head node;
+ struct mhi_dtr_ctrl_msg *rx_buf; /* DL sink, separate allocation for DMA safety */
+ struct delayed_work rx_refill; /* retries a failed sink buffer requeue */
+};
+
+static LIST_HEAD(mhi_wwan_dtr_list);
+static DEFINE_MUTEX(mhi_wwan_dtr_lock);
+
struct mhi_wwan_dev {
/* Lower level is a mhi dev, upper level is a wwan port */
struct mhi_device *mhi_dev;
@@ -35,6 +66,12 @@ struct mhi_wwan_dev {
* decremented on data queueing and incremented on data release.
*/
unsigned int rx_budget;
+
+ /* Latest DTR/RTS state for the modem, sent from dtr_work so a burst of
+ * changes is coalesced and only the final state has to get through
+ */
+ unsigned int dtr_mdmbits;
+ struct delayed_work dtr_work;
};
/* Increment RX budget and schedule RX refill if necessary */
@@ -103,6 +140,80 @@ static void mhi_wwan_ctrl_refill_work(struct work_struct *work)
}
}
+/* Signal the modem's DTR/RTS lines over its own controller's IP_CTRL channel */
+static int mhi_wwan_ctrl_send_dtr(struct mhi_wwan_dev *mhiwwan, unsigned int mdmbits)
+{
+ struct mhi_controller *cntrl = mhiwwan->mhi_dev->mhi_cntrl;
+ struct mhi_device *ctrl_dev = NULL;
+ struct mhi_dtr_ctrl_msg *dtr_msg;
+ struct mhi_wwan_dtr *dtr;
+ u32 msg = 0;
+ int ret;
+
+ guard(mutex)(&mhi_wwan_dtr_lock);
+
+ list_for_each_entry(dtr, &mhi_wwan_dtr_list, node) {
+ if (dtr->cntrl == cntrl) {
+ ctrl_dev = dtr->mhi_dev;
+ break;
+ }
+ }
+ if (!ctrl_dev) {
+ dev_dbg(&mhiwwan->mhi_dev->dev,
+ "IP_CTRL not enumerated; DTR/RTS not signalled to modem\n");
+ return 0;
+ }
+
+ dtr_msg = kzalloc_obj(*dtr_msg);
+ if (!dtr_msg)
+ return -ENOMEM;
+
+ if (mdmbits & TIOCM_DTR)
+ msg |= MHI_DTR_MSG_DTR;
+ if (mdmbits & TIOCM_RTS)
+ msg |= MHI_DTR_MSG_RTS;
+
+ dtr_msg->preamble = cpu_to_le32(MHI_DTR_CTRL_MAGIC);
+ dtr_msg->msg_id = cpu_to_le32(MHI_DTR_HOST_STATE);
+ dtr_msg->dest_id = cpu_to_le32(mhiwwan->mhi_dev->ul_chan_id);
+ dtr_msg->size = cpu_to_le32(sizeof(__le32));
+ dtr_msg->msg = cpu_to_le32(msg);
+
+ ret = mhi_queue_buf(ctrl_dev, DMA_TO_DEVICE, dtr_msg, sizeof(*dtr_msg),
+ MHI_EOT);
+ if (ret)
+ kfree(dtr_msg);
+
+ return ret;
+}
+
+/* Send the latest DTR/RTS state. A full UL ring or a failed allocation is
+ * retried shortly, with whatever state is latest by then, so the modem ends
+ * up with the final state. Other errors mean the channel is going away.
+ */
+static void mhi_wwan_ctrl_dtr_work(struct work_struct *work)
+{
+ struct mhi_wwan_dev *mhiwwan = container_of(to_delayed_work(work),
+ struct mhi_wwan_dev, dtr_work);
+ int ret;
+
+ ret = mhi_wwan_ctrl_send_dtr(mhiwwan, READ_ONCE(mhiwwan->dtr_mdmbits));
+ if (ret == -EAGAIN || ret == -ENOMEM)
+ schedule_delayed_work(&mhiwwan->dtr_work, msecs_to_jiffies(10));
+ else if (ret)
+ dev_warn_ratelimited(&mhiwwan->mhi_dev->dev,
+ "failed to send DTR/RTS state to modem: %d\n",
+ ret);
+}
+
+static void mhi_wwan_ctrl_dtr_rts(struct wwan_port *port, unsigned int mdmbits)
+{
+ struct mhi_wwan_dev *mhiwwan = wwan_port_get_drvdata(port);
+
+ WRITE_ONCE(mhiwwan->dtr_mdmbits, mdmbits);
+ schedule_delayed_work(&mhiwwan->dtr_work, 0);
+}
+
static int mhi_wwan_ctrl_start(struct wwan_port *port)
{
struct mhi_wwan_dev *mhiwwan = wwan_port_get_drvdata(port);
@@ -163,6 +274,7 @@ static const struct wwan_port_ops wwan_pops = {
.start = mhi_wwan_ctrl_start,
.stop = mhi_wwan_ctrl_stop,
.tx = mhi_wwan_ctrl_tx,
+ .dtr_rts = mhi_wwan_ctrl_dtr_rts,
};
static void mhi_ul_xfer_cb(struct mhi_device *mhi_dev,
@@ -224,6 +336,7 @@ static int mhi_wwan_ctrl_probe(struct mhi_device *mhi_dev,
mhiwwan->mhi_dev = mhi_dev;
mhiwwan->mtu = MHI_WWAN_MAX_MTU;
INIT_WORK(&mhiwwan->rx_refill, mhi_wwan_ctrl_refill_work);
+ INIT_DELAYED_WORK(&mhiwwan->dtr_work, mhi_wwan_ctrl_dtr_work);
spin_lock_init(&mhiwwan->tx_lock);
spin_lock_init(&mhiwwan->rx_lock);
@@ -252,9 +365,118 @@ static void mhi_wwan_ctrl_remove(struct mhi_device *mhi_dev)
struct mhi_wwan_dev *mhiwwan = dev_get_drvdata(&mhi_dev->dev);
wwan_remove_port(mhiwwan->wwan_port);
+ /* Give the DTR/RTS drop from removal one attempt, then stop the work */
+ flush_delayed_work(&mhiwwan->dtr_work);
+ disable_delayed_work_sync(&mhiwwan->dtr_work);
kfree(mhiwwan);
}
+/* IP_CTRL channel driver, bound separately so the data-port path is untouched */
+static void mhi_wwan_dtr_ul_xfer_cb(struct mhi_device *mhi_dev,
+ struct mhi_result *mhi_result)
+{
+ /* MHI core has done with the buffer, release it */
+ kfree(mhi_result->buf_addr);
+}
+
+/* Requeue the single DL sink buffer so the IP_CTRL DL ring never runs empty.
+ * This only runs from the work item, so remove() can stop it before the
+ * channel is torn down. A transient mapping failure is retried, other errors
+ * mean the channel is going away and the next probe queues a fresh buffer.
+ */
+static void mhi_wwan_dtr_refill_work(struct work_struct *work)
+{
+ struct mhi_wwan_dtr *dtr = container_of(to_delayed_work(work),
+ struct mhi_wwan_dtr, rx_refill);
+ int ret;
+
+ ret = mhi_queue_buf(dtr->mhi_dev, DMA_FROM_DEVICE, dtr->rx_buf,
+ sizeof(*dtr->rx_buf), MHI_EOT);
+ if (ret == -ENOMEM) {
+ dev_warn_ratelimited(&dtr->mhi_dev->dev,
+ "failed to requeue IP_CTRL RX buffer, retrying\n");
+ schedule_delayed_work(&dtr->rx_refill, msecs_to_jiffies(100));
+ } else if (ret) {
+ dev_dbg(&dtr->mhi_dev->dev,
+ "failed to requeue IP_CTRL RX buffer: %d\n", ret);
+ }
+}
+
+static void mhi_wwan_dtr_dl_xfer_cb(struct mhi_device *mhi_dev,
+ struct mhi_result *mhi_result)
+{
+ struct mhi_wwan_dtr *dtr = dev_get_drvdata(&mhi_dev->dev);
+
+ /* Channel is being torn down (e.g. -ENOTCONN), do not requeue */
+ if (mhi_result->transaction_status &&
+ mhi_result->transaction_status != -EOVERFLOW)
+ return;
+
+ /* Modem serial state not needed, requeue the sink buffer to keep DL ring live */
+ schedule_delayed_work(&dtr->rx_refill, 0);
+}
+
+static int mhi_wwan_dtr_probe(struct mhi_device *mhi_dev,
+ const struct mhi_device_id *id)
+{
+ struct mhi_wwan_dtr *dtr;
+ int ret;
+
+ dtr = kzalloc_obj(*dtr);
+ if (!dtr)
+ return -ENOMEM;
+
+ dtr->rx_buf = kmalloc_obj(*dtr->rx_buf);
+ if (!dtr->rx_buf) {
+ ret = -ENOMEM;
+ goto err_free_dtr;
+ }
+ INIT_DELAYED_WORK(&dtr->rx_refill, mhi_wwan_dtr_refill_work);
+
+ ret = mhi_prepare_for_transfer(mhi_dev);
+ if (ret)
+ goto err_free_buf;
+
+ dtr->cntrl = mhi_dev->mhi_cntrl;
+ dtr->mhi_dev = mhi_dev;
+ dev_set_drvdata(&mhi_dev->dev, dtr);
+
+ /* On failure mhi_probe() unprepares the channels itself */
+ ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, dtr->rx_buf,
+ sizeof(*dtr->rx_buf), MHI_EOT);
+ if (ret)
+ goto err_free_buf;
+
+ mutex_lock(&mhi_wwan_dtr_lock);
+ list_add(&dtr->node, &mhi_wwan_dtr_list);
+ mutex_unlock(&mhi_wwan_dtr_lock);
+
+ return 0;
+
+err_free_buf:
+ kfree(dtr->rx_buf);
+err_free_dtr:
+ kfree(dtr);
+ return ret;
+}
+
+static void mhi_wwan_dtr_remove(struct mhi_device *mhi_dev)
+{
+ struct mhi_wwan_dtr *dtr = dev_get_drvdata(&mhi_dev->dev);
+
+ mutex_lock(&mhi_wwan_dtr_lock);
+ list_del(&dtr->node);
+ mutex_unlock(&mhi_wwan_dtr_lock);
+
+ /* Stop the refill work, and stop a late DL callback re-arming it, before
+ * the channel is torn down so no requeue can race with the teardown
+ */
+ disable_delayed_work_sync(&dtr->rx_refill);
+ mhi_unprepare_from_transfer(mhi_dev);
+ kfree(dtr->rx_buf);
+ kfree(dtr);
+}
+
static const struct mhi_device_id mhi_wwan_ctrl_match_table[] = {
{ .chan = "DUN", .driver_data = WWAN_PORT_AT },
{ .chan = "DUN2", .driver_data = WWAN_PORT_AT },
@@ -278,7 +500,45 @@ static struct mhi_driver mhi_wwan_ctrl_driver = {
},
};
-module_mhi_driver(mhi_wwan_ctrl_driver);
+static const struct mhi_device_id mhi_wwan_dtr_match_table[] = {
+ { .chan = "IP_CTRL" },
+ {},
+};
+MODULE_DEVICE_TABLE(mhi, mhi_wwan_dtr_match_table);
+
+static struct mhi_driver mhi_wwan_dtr_driver = {
+ .id_table = mhi_wwan_dtr_match_table,
+ .remove = mhi_wwan_dtr_remove,
+ .probe = mhi_wwan_dtr_probe,
+ .ul_xfer_cb = mhi_wwan_dtr_ul_xfer_cb,
+ .dl_xfer_cb = mhi_wwan_dtr_dl_xfer_cb,
+ .driver = {
+ .name = "mhi_wwan_dtr",
+ },
+};
+
+static int __init mhi_wwan_ctrl_init(void)
+{
+ int ret;
+
+ ret = mhi_driver_register(&mhi_wwan_dtr_driver);
+ if (ret)
+ return ret;
+
+ ret = mhi_driver_register(&mhi_wwan_ctrl_driver);
+ if (ret)
+ mhi_driver_unregister(&mhi_wwan_dtr_driver);
+
+ return ret;
+}
+module_init(mhi_wwan_ctrl_init);
+
+static void __exit mhi_wwan_ctrl_exit(void)
+{
+ mhi_driver_unregister(&mhi_wwan_ctrl_driver);
+ mhi_driver_unregister(&mhi_wwan_dtr_driver);
+}
+module_exit(mhi_wwan_ctrl_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MHI WWAN CTRL Driver");
diff --git a/drivers/net/wwan/wwan_core.c b/drivers/net/wwan/wwan_core.c
index ffbcf11e4e68..7097b58dafde 100644
--- a/drivers/net/wwan/wwan_core.c
+++ b/drivers/net/wwan/wwan_core.c
@@ -25,6 +25,10 @@
#include <net/rtnetlink.h>
#include <uapi/linux/wwan.h>
+/* Modem control bits userspace may set on an AT port, as for tty_tiocmset() */
+#define WWAN_TIOCM_OUTPUTS (TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 | \
+ TIOCM_OUT2 | TIOCM_LOOP)
+
/* Maximum number of minors in use */
#define WWAN_MAX_MINORS (1 << MINORBITS)
@@ -73,11 +77,10 @@ struct wwan_device {
* @start_count: Port start counter
* @flags: Store port state and capabilities
* @ops: Pointer to WWAN port operations
- * @ops_lock: Protect port ops
+ * @ops_lock: Protect port ops, and the AT/QCDM termios and TIOCM state
* @dev: Underlying device
* @rxq: Buffer inbound queue
* @waitqueue: The waitqueue for port fops (read/write/poll)
- * @data_lock: Port specific data access serialization
* @headroom_len: SKB reserved headroom size
* @frag_len: Length to fragment packet
* @at_data: AT port specific data
@@ -88,11 +91,10 @@ struct wwan_port {
unsigned int start_count;
unsigned long flags;
const struct wwan_port_ops *ops;
- struct mutex ops_lock; /* Serialize ops + protect against removal */
+ struct mutex ops_lock; /* Serialize ops + at_data, protect against removal */
struct device dev;
struct sk_buff_head rxq;
wait_queue_head_t waitqueue;
- struct mutex data_lock; /* Port specific data access serialization */
size_t headroom_len;
size_t frag_len;
union {
@@ -374,7 +376,6 @@ static void wwan_port_destroy(struct device *dev)
if (dev->class == &wwan_class)
ida_free(&minors, MINOR(dev->devt));
- mutex_destroy(&port->data_lock);
mutex_destroy(&port->ops_lock);
kfree(port);
}
@@ -653,7 +654,10 @@ struct wwan_port *wwan_create_port(struct device *parent,
mutex_init(&port->ops_lock);
skb_queue_head_init(&port->rxq);
init_waitqueue_head(&port->waitqueue);
- mutex_init(&port->data_lock);
+
+ /* AT ports default to HUPCL, so DTR/RTS are dropped on last close */
+ if (type == WWAN_PORT_AT)
+ port->at_data.termios.c_cflag = HUPCL;
port->dev.parent = &wwandev->dev;
port->dev.type = &wwan_port_dev_type;
@@ -679,12 +683,44 @@ error_wwandev_remove:
}
EXPORT_SYMBOL_GPL(wwan_create_port);
+/* Raise DTR/RTS when an AT port is first opened, as a TTY does on open.
+ * Unlike a TTY the termios baud rate is not used. Called with ops_lock held.
+ */
+static void wwan_port_raise_dtr_rts(struct wwan_port *port)
+{
+ lockdep_assert_held(&port->ops_lock);
+
+ if (port->type != WWAN_PORT_AT || !port->ops->dtr_rts)
+ return;
+
+ port->at_data.mdmbits |= TIOCM_DTR | TIOCM_RTS;
+ port->ops->dtr_rts(port, port->at_data.mdmbits);
+}
+
+/* Drop DTR/RTS on an AT port. Called with ops_lock held. On last close
+ * the lines are only dropped if HUPCL is set, as for a TTY. When a port is
+ * removed while still open they are always dropped.
+ */
+static void wwan_port_drop_dtr_rts(struct wwan_port *port, bool hupcl_only)
+{
+ lockdep_assert_held(&port->ops_lock);
+
+ if (port->type != WWAN_PORT_AT || !port->ops->dtr_rts)
+ return;
+ if (hupcl_only && !(port->at_data.termios.c_cflag & HUPCL))
+ return;
+
+ port->at_data.mdmbits &= ~(TIOCM_DTR | TIOCM_RTS);
+ port->ops->dtr_rts(port, port->at_data.mdmbits);
+}
+
void wwan_remove_port(struct wwan_port *port)
{
struct wwan_device *wwandev = to_wwan_dev(port->dev.parent);
mutex_lock(&port->ops_lock);
if (port->start_count) {
+ wwan_port_drop_dtr_rts(port, false);
port->ops->stop(port);
port->start_count = 0;
}
@@ -756,8 +792,11 @@ static int wwan_port_op_start(struct wwan_port *port)
}
/* If port is already started, don't start again */
- if (!port->start_count)
+ if (!port->start_count) {
ret = port->ops->start(port);
+ if (!ret)
+ wwan_port_raise_dtr_rts(port);
+ }
if (!ret)
port->start_count++;
@@ -773,8 +812,10 @@ static void wwan_port_op_stop(struct wwan_port *port)
mutex_lock(&port->ops_lock);
port->start_count--;
if (!port->start_count) {
- if (port->ops)
+ if (port->ops) {
+ wwan_port_drop_dtr_rts(port, true);
port->ops->stop(port);
+ }
skb_queue_purge(&port->rxq);
clear_bit(WWAN_PORT_EXCLUSIVE, &port->flags);
}
@@ -976,92 +1017,109 @@ static __poll_t wwan_port_fops_poll(struct file *filp, poll_table *wait)
}
/* Implements minimalistic stub terminal IOCTLs support */
-static long wwan_port_fops_at_ioctl(struct wwan_port *port, unsigned int cmd,
- unsigned long arg)
+/* TCGETS/TCSETS and their termios2 variants. @size is the size of the user
+ * structure, a prefix of struct ktermios. As for the TIOCM ioctls, user
+ * memory is only accessed outside ops_lock.
+ */
+static long wwan_port_termios_ioctl(struct wwan_port *port, bool set,
+ void __user *uarg, size_t size)
{
- int ret = 0;
+ struct ktermios termios;
- mutex_lock(&port->data_lock);
+ if (!set) {
+ scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &port->ops_lock)
+ termios = port->at_data.termios;
+ return copy_to_user(uarg, &termios, size) ? -EFAULT : 0;
+ }
+
+ /* Commit only on success, a partial copy must not clear HUPCL */
+ if (copy_from_user(&termios, uarg, size))
+ return -EFAULT;
+ scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &port->ops_lock)
+ memcpy(&port->at_data.termios, &termios, size);
+ return 0;
+}
+static long wwan_port_fops_at_ioctl(struct wwan_port *port, unsigned int cmd,
+ unsigned long arg)
+{
+ /* ops_lock serialises the termios/TIOCM state with port ops and
+ * removal. It is only held while that state is read or updated, never
+ * across a user copy, and is taken interruptibly as a blocking write
+ * holds it too.
+ */
switch (cmd) {
case TCFLSH:
- break;
+ return 0;
case TCGETS:
- if (copy_to_user((void __user *)arg, &port->at_data.termios,
- sizeof(struct termios)))
- ret = -EFAULT;
- break;
-
case TCSETS:
case TCSETSW:
case TCSETSF:
- if (copy_from_user(&port->at_data.termios, (void __user *)arg,
- sizeof(struct termios)))
- ret = -EFAULT;
- break;
+ return wwan_port_termios_ioctl(port, cmd != TCGETS,
+ (void __user *)arg,
+ sizeof(struct termios));
#ifdef TCGETS2
case TCGETS2:
- if (copy_to_user((void __user *)arg, &port->at_data.termios,
- sizeof(struct termios2)))
- ret = -EFAULT;
- break;
-
case TCSETS2:
case TCSETSW2:
case TCSETSF2:
- if (copy_from_user(&port->at_data.termios, (void __user *)arg,
- sizeof(struct termios2)))
- ret = -EFAULT;
- break;
+ return wwan_port_termios_ioctl(port, cmd != TCGETS2,
+ (void __user *)arg,
+ sizeof(struct termios2));
#endif
- case TIOCMGET:
- ret = put_user(port->at_data.mdmbits, (int __user *)arg);
- break;
+ case TIOCMGET: {
+ int mdmbits;
+
+ scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &port->ops_lock)
+ mdmbits = port->at_data.mdmbits;
+ return put_user(mdmbits, (int __user *)arg);
+ }
case TIOCMSET:
case TIOCMBIC:
case TIOCMBIS: {
int mdmbits;
- if (copy_from_user(&mdmbits, (int __user *)arg, sizeof(int))) {
- ret = -EFAULT;
- break;
+ if (get_user(mdmbits, (int __user *)arg))
+ return -EFAULT;
+ mdmbits &= WWAN_TIOCM_OUTPUTS;
+
+ scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &port->ops_lock) {
+ int old = port->at_data.mdmbits;
+
+ if (cmd == TIOCMBIC)
+ port->at_data.mdmbits &= ~mdmbits;
+ else if (cmd == TIOCMBIS)
+ port->at_data.mdmbits |= mdmbits;
+ else
+ port->at_data.mdmbits = mdmbits;
+ /* Only signal the driver when DTR or RTS actually changes */
+ if (port->type == WWAN_PORT_AT && port->ops &&
+ port->ops->dtr_rts &&
+ (old ^ port->at_data.mdmbits) & (TIOCM_DTR | TIOCM_RTS))
+ port->ops->dtr_rts(port, port->at_data.mdmbits);
}
- if (cmd == TIOCMBIC)
- port->at_data.mdmbits &= ~mdmbits;
- else if (cmd == TIOCMBIS)
- port->at_data.mdmbits |= mdmbits;
- else
- port->at_data.mdmbits = mdmbits;
- break;
+ return 0;
}
case TIOCEXCL:
set_bit(WWAN_PORT_EXCLUSIVE, &port->flags);
- break;
+ return 0;
case TIOCNXCL:
clear_bit(WWAN_PORT_EXCLUSIVE, &port->flags);
- break;
+ return 0;
case TIOCGEXCL:
- {
- int excl = test_bit(WWAN_PORT_EXCLUSIVE, &port->flags);
-
- ret = put_user(excl, (int __user *)arg);
- break;
- }
+ return put_user(test_bit(WWAN_PORT_EXCLUSIVE, &port->flags),
+ (int __user *)arg);
default:
- ret = -ENOIOCTLCMD;
+ return -ENOIOCTLCMD;
}
-
- mutex_unlock(&port->data_lock);
-
- return ret;
}
static long wwan_port_fops_ioctl(struct file *filp, unsigned int cmd,
diff --git a/drivers/net/wwan/wwan_hwsim.c b/drivers/net/wwan/wwan_hwsim.c
index c3c2d2a68767..34eea3edc824 100644
--- a/drivers/net/wwan/wwan_hwsim.c
+++ b/drivers/net/wwan/wwan_hwsim.c
@@ -500,6 +500,7 @@ static ssize_t wwan_hwsim_debugfs_portdestroy_write(struct file *file,
}
static const struct file_operations wwan_hwsim_debugfs_portdestroy_fops = {
+ .owner = THIS_MODULE,
.write = wwan_hwsim_debugfs_portdestroy_write,
.open = simple_open,
.llseek = noop_llseek,
@@ -524,6 +525,7 @@ static ssize_t wwan_hwsim_debugfs_portcreate_write(struct file *file,
}
static const struct file_operations wwan_hwsim_debugfs_portcreate_fops = {
+ .owner = THIS_MODULE,
.write = wwan_hwsim_debugfs_portcreate_write,
.open = simple_open,
.llseek = noop_llseek,
@@ -545,6 +547,7 @@ static ssize_t wwan_hwsim_debugfs_devdestroy_write(struct file *file,
}
static const struct file_operations wwan_hwsim_debugfs_devdestroy_fops = {
+ .owner = THIS_MODULE,
.write = wwan_hwsim_debugfs_devdestroy_write,
.open = simple_open,
.llseek = noop_llseek,
@@ -568,6 +571,7 @@ static ssize_t wwan_hwsim_debugfs_devcreate_write(struct file *file,
}
static const struct file_operations wwan_hwsim_debugfs_devcreate_fops = {
+ .owner = THIS_MODULE,
.write = wwan_hwsim_debugfs_devcreate_write,
.open = simple_open,
.llseek = noop_llseek,