>From 250a8a1214ffb80bfc93e7d93f01796564dabd24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Mork?= Date: Fri, 16 Dec 2011 16:31:43 +0100 Subject: [PATCH] qmi_wwan: allow QMI proxying via netdev ioctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Proxy QMI requests received via ioctl to embedded CDC commands, and return the embedded CDC responses. Will not allow QMI_CTL commands. Shared subsystem client IDs are allocated on-demand by the driver. Signed-off-by: Bjørn Mork --- drivers/net/usb/qmi.c | 118 +++++++++++++++++++++++++++++++++++++++ drivers/net/usb/qmi.h | 6 ++ drivers/net/usb/qmi_wwan_core.c | 60 ++++++++++++++++++++ 3 files changed, 184 insertions(+), 0 deletions(-) diff --git a/drivers/net/usb/qmi.c b/drivers/net/usb/qmi.c index 78269a3..b3c022b 100644 --- a/drivers/net/usb/qmi.c +++ b/drivers/net/usb/qmi.c @@ -459,6 +459,10 @@ static int qmi_ctl_request_cid(struct qmi_state *qmi, u8 system) u8 tlvdata[1] = { system }; char buf[32]; + /* fail if we have no slot for the cid */ + if (system >= sizeof(qmi->cid)) + return -1; + /* return immediately if a CID is already allocated */ if (qmi->cid[system]) return qmi->cid[system]; @@ -579,6 +583,120 @@ static int qmi_dms_verify_pin(struct qmi_state *qmi) /* ----- exported API ----- */ +/* proxy QMI requests, using our own client and transaction IDs */ +int qmi_ioctl_proxy(struct qmi_state *qmi, void __user *arg, size_t buflen) +{ + char *buf; + struct qmux *h; + struct qmi_msg *m; + size_t len; + u8 ucid; + __le16 utid, tid; + unsigned long flags = qmi->flags; + int ret = -EFAULT; + int timeout = 30; + + /* we don't allow QMI_CTL, so the minimum QMI message size is: */ + if (buflen < sizeof(*buf) + sizeof(*m)) + return -EINVAL; + + + buf = kmalloc(buflen, GFP_ATOMIC); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, arg, buflen)) + goto error; + + /* verify that what we got is a valid QMI message and that we have a CID for the system */ + h = (struct qmux *)buf; + len = h->len + 1; /* total QMUX length including the tf byte */ + + + + /* sanity. Note that we disallow the QMI_CTL subsystem */ + if (h->tf != 0x01 || len > buflen || h->service == QMI_CTL) { + ret = -EINVAL; + goto error; + } + + m = (struct qmi_msg *)h->msg; + + /* require an exact length match */ + if (sizeof(*h) + sizeof(*m) + m->len != len) { + ret = -EINVAL; + goto error; + } + + /* attempt to get a client ID for the requested subsystem */ + if (qmi_ctl_request_cid(qmi, h->service) < 0) + goto error; + + /* use our private cid and tid, saving the user's so that we can restore them on return */ + ucid = h->qmicid; + utid = h->tid.w; + tid = new_tid(); + h->qmicid = qmi->cid[h->service]; + h->tid.w = tid; + + /* turn off async reads and send the request */ + qmi->flags &= ~QMI_FLAG_RECV; + if (usb_control_msg(qmi->dev, usb_sndctrlpipe(qmi->dev, 0), + USB_CDC_SEND_ENCAPSULATED_COMMAND, + USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE, + 0, qmi->intfnr, buf, len, 1000) != len) + goto error; + + /* read the reply into the same buffer */ + + /* wait a while for the (correct) reply */ + do { + ret = usb_control_msg(qmi->dev, usb_rcvctrlpipe(qmi->dev, 0), + USB_CDC_GET_ENCAPSULATED_RESPONSE, USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE, + 0, qmi->intfnr, buf, buflen, 1000); + if (ret < 0) + goto error; + + m = qmi_qmux_verify(buf, ret); + if (m && h->tid.w == tid) /* got it */ + break; + + /* handle other packets returned while waiting for the correct one */ + if (ret && m) { + switch (h->service) { + case QMI_CTL: + qmi_ctl_parse(qmi, m); + break; + case QMI_WDS: + qmi_wds_parse(qmi, m); + break; + case QMI_DMS: + qmi_dms_parse(qmi, m); + } + if (qmi_debug) + qmi_dump_qmux(qmi, buf, h->len + 1); + } + msleep(100); + } while (timeout-- > 0); + if (timeout == 0) + goto error; + + /* restore the users input to hide our internal id's */ + h->qmicid = ucid; + h->tid.w = utid; + + /* return the reply to the user */ + if (copy_to_user(arg, buf, h->len + 1)) + goto error; + + ret = 0; + +error: + qmi->flags = flags; + kfree(buf); + return ret; +} + int qmi_reset(struct qmi_state *qmi) { /* NOTE: We do NOT clear the allocated CIDs! */ diff --git a/drivers/net/usb/qmi.h b/drivers/net/usb/qmi.h index 53ebafe..bf9c8fd 100644 --- a/drivers/net/usb/qmi.h +++ b/drivers/net/usb/qmi.h @@ -82,6 +82,12 @@ struct qmi_tlv_response_data { } __packed; +/* extension */ +#define IF_IFACE_WWAN 0x1042 + +/* proxy QMI requests via ioctl */ +extern int qmi_ioctl_proxy(struct qmi_state *qmi, void __user *arg, size_t buflen); + /* reset state to INIT */ extern int qmi_reset(struct qmi_state *qmi); diff --git a/drivers/net/usb/qmi_wwan_core.c b/drivers/net/usb/qmi_wwan_core.c index 6d96465..79c3b1a 100644 --- a/drivers/net/usb/qmi_wwan_core.c +++ b/drivers/net/usb/qmi_wwan_core.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -46,6 +47,57 @@ static void qmi_wwan_cdc_status(struct usbnet *dev, struct urb *urb) } } +static int qmi_wwan_ioctl(struct net_device *net, struct ifreq *ifr, int cmd) +{ + struct usbnet *dev = netdev_priv(net); + struct if_settings *settings = &ifr->ifr_settings; + struct qmi_wwan_state *info = (void *)&dev->data; + + switch (cmd) { + case SIOCDEVPRIVATE + 0: /* get something to verify that the private ioctl protocol */ + /* FIMXE: implement this! */ + return 0; + + case SIOCDEVPRIVATE + 1: /* set SIM PIN */ + /* FIMXE: implement this! */ + return 0; + + case SIOCDEVPRIVATE + 2: /* set APN */ + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + /* cannot change APN if interface is up */ + if (net->flags & IFF_UP) + return -EBUSY; + + /* FIMXE: implement this! */ + return 0; + + case SIOCDEVPRIVATE + 3: /* proxy QMI */ + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + if (settings->type != IF_IFACE_WWAN) + return -EINVAL; + netdev_dbg(net, "proxying userspace QMI request\n"); + return qmi_ioctl_proxy(info->qmi, settings->ifs_ifsu.fr, settings->size); + + default: + netdev_dbg(net, "ioctl 0x%08x\n", cmd); + } + return -EINVAL; +} + +/* same as usbnet_netdev_ops but ioctl added */ +static const struct net_device_ops qmi_wwan_netdev_ops = { + .ndo_open = usbnet_open, + .ndo_stop = usbnet_stop, + .ndo_start_xmit = usbnet_start_xmit, + .ndo_tx_timeout = usbnet_tx_timeout, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, + .ndo_do_ioctl = qmi_wwan_ioctl, +}; + static int qmi_wwan_cdc_ecmlike_bind(struct usbnet *dev, struct usb_interface *intf) { int status; @@ -61,6 +113,10 @@ static int qmi_wwan_cdc_ecmlike_bind(struct usbnet *dev, struct usb_interface *i usbnet_cdc_unbind(dev, intf); return -1; } + + /* override default usbnet ops so that we can handle ioctls */ + dev->net->netdev_ops = &qmi_wwan_netdev_ops; + return 0; } @@ -80,6 +136,10 @@ static int qmi_wwan_cdc_bind(struct usbnet *dev, struct usb_interface *intf) return -1; info->control = intf; + + /* override default usbnet ops so that we can handle ioctls */ + dev->net->netdev_ops = &qmi_wwan_netdev_ops; + return 0; } -- 1.7.7.3