diff mbox

[3/3] qmi_wwan: Driver for WWAN devices requiring use of the QMI protocol

Message ID 874nx0bj1d.fsf@nemi.mork.no
State Superseded, archived
Delegated to: David Miller
Headers show

Commit Message

Bjørn Mork Dec. 16, 2011, 4:03 p.m. UTC
Dan Williams <dcbw@redhat.com> writes:
> On Thu, 2011-12-15 at 11:02 +0100, Bjørn Mork wrote:
>
> But I agree that eventually the full QMI protocol should be made
>> available to userspace for other uses.  That should be fairly easy to do
>> if you just proxy the commands.  But I'm worring about the interface.
>> Is the /dev/qmi from GobiNet acceptable?  Why isn't it merged yet?
>
> It would have to be /dev/qmiX (in case you have more than one
> QMI-capable card in the system) and it would also have to have the right
> sysfs entries so that we could match the qmiX entry up with it's parent
> USB interface.  Not entirely sure how to do that.

The idea of creating multiple independent devices and then stitch them
together again using a sysfs API seems a little backwards to me.  How
about just use ioctls and forward both request and reply?  That won't
support unsolicited notifications, but otherwise it should be enough to
be useful.

The attached patch implements straight QMI forwarding.  Note that it is
only intended as a demo, and *not* a submission.  I have not yet decided
whether this is a good idea or not, and I assume that the API should
receive some more thought and blessings from others than me before
anything like this can be submitted.

It should maybe even be extended to something a little less
driver/device specific.  I believe a common WWAN API for settings things
like PIN code and APN would be very useful, in the same way the wireless
API has made WLAN usage possible without driver specific tools.


But anyway, the demo does work.  Using it to send this reqest for
firmware revision from userspace:

0000  01 0c 00 00 02 00 00 01  00 23 00 00 00

partly decoded as:

.tf = 0x01
.len = 0x000c
.ctrl = 0x00 control point
.service = 0x02
.qmicid = 0x00
.flags = 0x00 request
.tid = 0x0001
.msgid = 0x0023
.len = 000000


I get the expected reply:

0000  01 4c 00 80 02 00 02 01  00 23 00 40 00 02 04 00
0010  00 00 00 00 01 36 00 4d  39 32 30 30 42 2d 53 43
0020  41 51 44 42 5a 44 2d 33  2e 30 2e 33 35 30 30 32
0030  35 54 20 20 31 20 20 5b  41 75 67 20 31 31 20 32
0040  30 31 31 20 30 32 3a 30  30 3a 30 30 5d

partly decoded as:

.tf = 0x01
.len = 0x004c
.ctrl = 0x80 service
.service = 0x02
.qmicid = 0x00
.flags = 0x02 response
.tid = 0x0001
.msgid = 0x0023
.len = 0x0040
[0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
[0x01] (54) 4d 39 32 30 30 42 2d 53 43 41 51 44 42 5a 44 2d 33 2e 30 2e 33 35 30 30 32 35 54 20 20 31 20 20 5b 41 75 67 20 31 31 20 32 30 31 31 20 30 32 3a 30 30 3a 30 30 5d   M9200B-SCAQDBZD-3.0.350025T  1  [Aug 11 2011 02:00:00]


This is while having an open connection and sending traffic over it (not
that that should matter, but anyway..)

> Huawei writes custom firmware for their dongles. 

Somehow I find that hard to believe.  I can believe that they *build* a
custom firmware, enabling a vendor specific set of options, setting
their own USB descriptors etc.  And maybe even write some vendor
specific feature.  But I would be surprised if most of their firmware
code didn't come directly from the chipset vendor example code.

And the same goes for every other dongle maker.

> Gobi devices and other
> devices that talk QMI don't  necessarily have such a full quite of AT
> commands, yet they all talk the same QMI protocol.  It makes sense to
> have a generic driver for this if we can.  That probably means a QMI
> core (like you've got with the qmi_wwan stuff) and device-specific
> drives.  The Huawei device would use the ECM-like stuff while the Gobi
> bits would implement what gobi_net does.  They might even be almost the
> same, I haven't looked in a while.  But they are similar enough that
> they should be sharing most of the code.

Yes, I absolutely agree. That's why I tried to keep the qmi specific
code as driver agnostic as possible.  Well, I could probably have done
better, but it's a start..



Bjørn

Comments

Dan Williams Dec. 16, 2011, 5:17 p.m. UTC | #1
On Fri, 2011-12-16 at 17:03 +0100, Bjørn Mork wrote:
> Dan Williams <dcbw@redhat.com> writes:
> > On Thu, 2011-12-15 at 11:02 +0100, Bjørn Mork wrote:
> >
> > But I agree that eventually the full QMI protocol should be made
> >> available to userspace for other uses.  That should be fairly easy to do
> >> if you just proxy the commands.  But I'm worring about the interface.
> >> Is the /dev/qmi from GobiNet acceptable?  Why isn't it merged yet?
> >
> > It would have to be /dev/qmiX (in case you have more than one
> > QMI-capable card in the system) and it would also have to have the right
> > sysfs entries so that we could match the qmiX entry up with it's parent
> > USB interface.  Not entirely sure how to do that.
> 
> The idea of creating multiple independent devices and then stitch them
> together again using a sysfs API seems a little backwards to me.  How
> about just use ioctls and forward both request and reply?  That won't
> support unsolicited notifications, but otherwise it should be enough to
> be useful.

Typically adding more ioctls isn't well received.  Plus I don't think
there's a huge reason to do so, because it's really just a stream
protocol that's easily handled from a file descriptor.  ioctls add
unecessary structure.  Netlink might be a better approach if we want
something there, but any time you think of adding an ioctl you need to
start questioning that thought :)  I just don't think ioctls are really
required in this instance.

And we don't really have to stitch devices together; the QMI device is
really just a file descriptor with read/write operations.  It's not any
different than a serial device like ttyUSB0.  And that fits just fine
into the sysfs hierarchy just like the USB interfaces from the modem do,
and the ethernet device from the modem does.  In sysfs they all have a
common ancestor: the USB device they are provided by. That allows
connection  managers to find out that ttyUSB0 and ttyUSB1 and usb0 are
all owned by the same device, and that say usb0 is the network device
that must be set up using ttyUSB0.  This is what allows hardware
autodetection, otherwise users have to sit around editing files in /etc
to tell the machine things it should already know.

> The attached patch implements straight QMI forwarding.  Note that it is
> only intended as a demo, and *not* a submission.  I have not yet decided
> whether this is a good idea or not, and I assume that the API should
> receive some more thought and blessings from others than me before
> anything like this can be submitted.
> 
> It should maybe even be extended to something a little less
> driver/device specific.  I believe a common WWAN API for settings things
> like PIN code and APN would be very useful, in the same way the wireless
> API has made WLAN usage possible without driver specific tools.

Unfortunately there's really too much variation in WWAN modems to make
this happen in the kernel.  It's really best left to userspace.  Plus,
there are a *ton* of quirks for different devices.  On some devices you
can send a command on any AT port, on others it has to be a specific AT
port.  AT parsing isn't something we should be doing in the kernel, and
most modems use AT at this point.  Others use QMI, some QCDM, some CnS,
some WMC, etc.  A common kernel-side WWAN API is just not going to
happen.  But in userspace we have various projects like ModemManager
that try to provide that generic API and abstract the differences
between modems.  That's my primary interest in this.

> 
> But anyway, the demo does work.  Using it to send this reqest for
> firmware revision from userspace:
> 
> 0000  01 0c 00 00 02 00 00 01  00 23 00 00 00
> 
> partly decoded as:
> 
> .tf = 0x01
> .len = 0x000c
> .ctrl = 0x00 control point
> .service = 0x02
> .qmicid = 0x00
> .flags = 0x00 request
> .tid = 0x0001
> .msgid = 0x0023
> .len = 000000
> 
> 
> I get the expected reply:
> 
> 0000  01 4c 00 80 02 00 02 01  00 23 00 40 00 02 04 00
> 0010  00 00 00 00 01 36 00 4d  39 32 30 30 42 2d 53 43
> 0020  41 51 44 42 5a 44 2d 33  2e 30 2e 33 35 30 30 32
> 0030  35 54 20 20 31 20 20 5b  41 75 67 20 31 31 20 32
> 0040  30 31 31 20 30 32 3a 30  30 3a 30 30 5d
> 
> partly decoded as:
> 
> .tf = 0x01
> .len = 0x004c
> .ctrl = 0x80 service
> .service = 0x02
> .qmicid = 0x00
> .flags = 0x02 response
> .tid = 0x0001
> .msgid = 0x0023
> .len = 0x0040
> [0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
> [0x01] (54) 4d 39 32 30 30 42 2d 53 43 41 51 44 42 5a 44 2d 33 2e 30 2e 33 35 30 30 32 35 54 20 20 31 20 20 5b 41 75 67 20 31 31 20 32 30 31 31 20 30 32 3a 30 30 3a 30 30 5d   M9200B-SCAQDBZD-3.0.350025T  1  [Aug 11 2011 02:00:00]
> 
> 
> This is while having an open connection and sending traffic over it (not
> that that should matter, but anyway..)
> 
> > Huawei writes custom firmware for their dongles. 
> 
> Somehow I find that hard to believe.  I can believe that they *build* a
> custom firmware, enabling a vendor specific set of options, setting
> their own USB descriptors etc.  And maybe even write some vendor
> specific feature.  But I would be surprised if most of their firmware
> code didn't come directly from the chipset vendor example code.

Yeah, I wasn't specific enough.  Everyone who uses a Qualcomm chipset
and licenses the firmware sources can modify it.  Some vendors just use
the straight Qualcomm firmware (mostly no-name Asian manufacturers).
But many vendors add their own AT commands, write custom protocols (CnS,
WMC, etc), some add custom network transports (Sierra), etc.  Huawei has
apparnetly decided to change the USB descriptors too.

> And the same goes for every other dongle maker.
> 
> > Gobi devices and other
> > devices that talk QMI don't  necessarily have such a full quite of AT
> > commands, yet they all talk the same QMI protocol.  It makes sense to
> > have a generic driver for this if we can.  That probably means a QMI
> > core (like you've got with the qmi_wwan stuff) and device-specific
> > drives.  The Huawei device would use the ECM-like stuff while the Gobi
> > bits would implement what gobi_net does.  They might even be almost the
> > same, I haven't looked in a while.  But they are similar enough that
> > they should be sharing most of the code.
> 
> Yes, I absolutely agree. That's why I tried to keep the qmi specific
> code as driver agnostic as possible.  Well, I could probably have done
> better, but it's a start..

So the reason gobi_net wasn't accepted was that with Gobi 1000 and 2000
chipsets, firmware needs to be loaded into the chip.  And that firmware
isn't free; you have to get it from the Windows partition of your
machine.  So davem rejected it because there was no way distros could
really test it easily since the firmware isn't freely available.

But now the Gobi 3000 stores firmware onboard, so it's worth another try
to get gobi_net upstreamed.

Dan


> 
> 
> Bjørn
> 
> differences between files attachment
> (0001-qmi_wwan-allow-QMI-proxying-via-netdev-ioctl.patch)
> From 250a8a1214ffb80bfc93e7d93f01796564dabd24 Mon Sep 17 00:00:00 2001
> From: =?UTF-8?q?Bj=C3=B8rn=20Mork?= <bjorn@mork.no>
> 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 <bjorn@mork.no>
> ---
>  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;
>  
> 
> +/* <linux/if.h> 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 <linux/module.h>
>  #include <linux/netdevice.h>
> +#include <linux/etherdevice.h>
>  #include <linux/ethtool.h>
>  #include <linux/mii.h>
>  #include <linux/usb.h>
> @@ -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;
>  }
>  


--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Dan Williams Jan. 4, 2012, 8:57 p.m. UTC | #2
On Fri, 2011-12-16 at 11:17 -0600, Dan Williams wrote:
> On Fri, 2011-12-16 at 17:03 +0100, Bjørn Mork wrote:
> > Dan Williams <dcbw@redhat.com> writes:
> > > On Thu, 2011-12-15 at 11:02 +0100, Bjørn Mork wrote:
> > >
> > > But I agree that eventually the full QMI protocol should be made
> > >> available to userspace for other uses.  That should be fairly easy to do
> > >> if you just proxy the commands.  But I'm worring about the interface.
> > >> Is the /dev/qmi from GobiNet acceptable?  Why isn't it merged yet?
> > >
> > > It would have to be /dev/qmiX (in case you have more than one
> > > QMI-capable card in the system) and it would also have to have the right
> > > sysfs entries so that we could match the qmiX entry up with it's parent
> > > USB interface.  Not entirely sure how to do that.
> > 
> > The idea of creating multiple independent devices and then stitch them
> > together again using a sysfs API seems a little backwards to me.  How
> > about just use ioctls and forward both request and reply?  That won't
> > support unsolicited notifications, but otherwise it should be enough to
> > be useful.
> 
> Typically adding more ioctls isn't well received.  Plus I don't think
> there's a huge reason to do so, because it's really just a stream
> protocol that's easily handled from a file descriptor.  ioctls add
> unecessary structure.  Netlink might be a better approach if we want
> something there, but any time you think of adding an ioctl you need to
> start questioning that thought :)  I just don't think ioctls are really
> required in this instance.
> 
> And we don't really have to stitch devices together; the QMI device is
> really just a file descriptor with read/write operations.  It's not any
> different than a serial device like ttyUSB0.  And that fits just fine
> into the sysfs hierarchy just like the USB interfaces from the modem do,
> and the ethernet device from the modem does.  In sysfs they all have a
> common ancestor: the USB device they are provided by. That allows
> connection  managers to find out that ttyUSB0 and ttyUSB1 and usb0 are
> all owned by the same device, and that say usb0 is the network device
> that must be set up using ttyUSB0.  This is what allows hardware
> autodetection, otherwise users have to sit around editing files in /etc
> to tell the machine things it should already know.
> 
> > The attached patch implements straight QMI forwarding.  Note that it is
> > only intended as a demo, and *not* a submission.  I have not yet decided
> > whether this is a good idea or not, and I assume that the API should
> > receive some more thought and blessings from others than me before
> > anything like this can be submitted.
> > 
> > It should maybe even be extended to something a little less
> > driver/device specific.  I believe a common WWAN API for settings things
> > like PIN code and APN would be very useful, in the same way the wireless
> > API has made WLAN usage possible without driver specific tools.
> 
> Unfortunately there's really too much variation in WWAN modems to make
> this happen in the kernel.  It's really best left to userspace.  Plus,
> there are a *ton* of quirks for different devices.  On some devices you
> can send a command on any AT port, on others it has to be a specific AT
> port.  AT parsing isn't something we should be doing in the kernel, and
> most modems use AT at this point.  Others use QMI, some QCDM, some CnS,
> some WMC, etc.  A common kernel-side WWAN API is just not going to
> happen.  But in userspace we have various projects like ModemManager
> that try to provide that generic API and abstract the differences
> between modems.  That's my primary interest in this.
> 
> > 
> > But anyway, the demo does work.  Using it to send this reqest for
> > firmware revision from userspace:
> > 
> > 0000  01 0c 00 00 02 00 00 01  00 23 00 00 00
> > 
> > partly decoded as:
> > 
> > .tf = 0x01
> > .len = 0x000c
> > .ctrl = 0x00 control point
> > .service = 0x02
> > .qmicid = 0x00
> > .flags = 0x00 request
> > .tid = 0x0001
> > .msgid = 0x0023
> > .len = 000000
> > 
> > 
> > I get the expected reply:
> > 
> > 0000  01 4c 00 80 02 00 02 01  00 23 00 40 00 02 04 00
> > 0010  00 00 00 00 01 36 00 4d  39 32 30 30 42 2d 53 43
> > 0020  41 51 44 42 5a 44 2d 33  2e 30 2e 33 35 30 30 32
> > 0030  35 54 20 20 31 20 20 5b  41 75 67 20 31 31 20 32
> > 0040  30 31 31 20 30 32 3a 30  30 3a 30 30 5d
> > 
> > partly decoded as:
> > 
> > .tf = 0x01
> > .len = 0x004c
> > .ctrl = 0x80 service
> > .service = 0x02
> > .qmicid = 0x00
> > .flags = 0x02 response
> > .tid = 0x0001
> > .msgid = 0x0023
> > .len = 0x0040
> > [0x02] (4) SUCCESS (0x0000) QMI_ERR_NONE
> > [0x01] (54) 4d 39 32 30 30 42 2d 53 43 41 51 44 42 5a 44 2d 33 2e 30 2e 33 35 30 30 32 35 54 20 20 31 20 20 5b 41 75 67 20 31 31 20 32 30 31 31 20 30 32 3a 30 30 3a 30 30 5d   M9200B-SCAQDBZD-3.0.350025T  1  [Aug 11 2011 02:00:00]
> > 
> > 
> > This is while having an open connection and sending traffic over it (not
> > that that should matter, but anyway..)
> > 
> > > Huawei writes custom firmware for their dongles. 
> > 
> > Somehow I find that hard to believe.  I can believe that they *build* a
> > custom firmware, enabling a vendor specific set of options, setting
> > their own USB descriptors etc.  And maybe even write some vendor
> > specific feature.  But I would be surprised if most of their firmware
> > code didn't come directly from the chipset vendor example code.
> 
> Yeah, I wasn't specific enough.  Everyone who uses a Qualcomm chipset
> and licenses the firmware sources can modify it.  Some vendors just use
> the straight Qualcomm firmware (mostly no-name Asian manufacturers).
> But many vendors add their own AT commands, write custom protocols (CnS,
> WMC, etc), some add custom network transports (Sierra), etc.  Huawei has
> apparnetly decided to change the USB descriptors too.
> 
> > And the same goes for every other dongle maker.
> > 
> > > Gobi devices and other
> > > devices that talk QMI don't  necessarily have such a full quite of AT
> > > commands, yet they all talk the same QMI protocol.  It makes sense to
> > > have a generic driver for this if we can.  That probably means a QMI
> > > core (like you've got with the qmi_wwan stuff) and device-specific
> > > drives.  The Huawei device would use the ECM-like stuff while the Gobi
> > > bits would implement what gobi_net does.  They might even be almost the
> > > same, I haven't looked in a while.  But they are similar enough that
> > > they should be sharing most of the code.
> > 
> > Yes, I absolutely agree. That's why I tried to keep the qmi specific
> > code as driver agnostic as possible.  Well, I could probably have done
> > better, but it's a start..
> 
> So the reason gobi_net wasn't accepted was that with Gobi 1000 and 2000
> chipsets, firmware needs to be loaded into the chip.  And that firmware
> isn't free; you have to get it from the Windows partition of your
> machine.  So davem rejected it because there was no way distros could
> really test it easily since the firmware isn't freely available.
> 
> But now the Gobi 3000 stores firmware onboard, so it's worth another try
> to get gobi_net upstreamed.

I spent some time with the Pantech UML290 (a dual-mode CDMA/LTE device
based off MSM9xxx) and its network port is basically the same as the
Huawei and the Gobi devices; it uses control transfers for the QMI
commands and bulk transfers for the network data.  Interestingly, the
UML290 uses the "rawip" mode instead of 802.3 mode, so the incoming and
outgoing data is simply raw IP packets.  The IPv4 and IPv6 addresses of
the interface are determined either through QMI requests (IPv4) or
through WMC requests on a separate port (IPv6).  I'm hopeful that we
could switch the device over to 802.3 mode by just sending the right QMI
TLV as part of the CTL/Set Data Format command (0x26) using TLV 0x01
(CTL/Set Data Format Request/Format) and TLV 0x10 (CTL/Set Data Format
Request/Protocol).  But I'm not sure, I haven't tried it yet, and it's
obviously not something that Pantech is actually testing otherwise the
Windows driver would use 802.3 mode.

But what this says to me is that we do need a generic QMI driver for
devices that handles the network traffic and also QMI passthrough.  And
then separate interface drivers that handle the USB-level quirks like
your Huawei driver.  We already have the "gobi_net" driver that Elly
Jones from Google cleaned up back in 2009, but it's not split out like
you've done here and I haven't looked into what changes would need to be
done for that.

I'm still somewhat uncomfortable with the amount of QMI logic in this
driver though, given that for the most part, we try to keep this sort of
stuff out of the kernel and in userland.  It also gives the wrong
impression that the interface can actually be used like a normal
interface, where for the most part it cannot unless there is additional
control logic in userspace.  I don't view it as any different from
AT-based WWAN devices in this regard and I think treating it differently
from them is not the right approach.

Dan


--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Bjørn Mork Jan. 5, 2012, 8:58 a.m. UTC | #3
Dan Williams <dcbw@redhat.com> writes:

> I spent some time with the Pantech UML290 (a dual-mode CDMA/LTE device
> based off MSM9xxx) and its network port is basically the same as the
> Huawei and the Gobi devices; it uses control transfers for the QMI
> commands and bulk transfers for the network data.  Interestingly, the
> UML290 uses the "rawip" mode instead of 802.3 mode, so the incoming and
> outgoing data is simply raw IP packets.

Interesting.  I assume that's the mode I have been seeing under certain
circumstances too.  But I've not yet discovered how to enable/disable
it.

> The IPv4 and IPv6 addresses of
> the interface are determined either through QMI requests (IPv4) or
> through WMC requests on a separate port (IPv6).  I'm hopeful that we
> could switch the device over to 802.3 mode by just sending the right QMI
> TLV as part of the CTL/Set Data Format command (0x26) using TLV 0x01
> (CTL/Set Data Format Request/Format) and TLV 0x10 (CTL/Set Data Format
> Request/Protocol).  But I'm not sure, I haven't tried it yet, and it's
> obviously not something that Pantech is actually testing otherwise the
> Windows driver would use 802.3 mode.

So the Windows driver uses this mode?  Then it could be something the
vendor chooses when building the firmware, and therefore not runtime
configurable at all.

> But what this says to me is that we do need a generic QMI driver for
> devices that handles the network traffic and also QMI passthrough.  And
> then separate interface drivers that handle the USB-level quirks like
> your Huawei driver.  We already have the "gobi_net" driver that Elly
> Jones from Google cleaned up back in 2009, but it's not split out like
> you've done here and I haven't looked into what changes would need to be
> done for that.

I have barely started looking at this, but christmas and a beach
vacation have been far more interesting lately :-)

> I'm still somewhat uncomfortable with the amount of QMI logic in this
> driver though, given that for the most part, we try to keep this sort of
> stuff out of the kernel and in userland.  It also gives the wrong
> impression that the interface can actually be used like a normal
> interface, where for the most part it cannot unless there is additional
> control logic in userspace.  I don't view it as any different from
> AT-based WWAN devices in this regard and I think treating it differently
> from them is not the right approach.

Yes, agree fully that my first draft had too much QMI logic built-in.  I
do want to keep the bare minimum to make the driver work out-of-the-box
with "ip link set dev wwanX up", but nothing more than that.

I started thinking about just exporting the raw embedded protocol as a
char device, making the exported device completely protocol agnostic
(just a wrapper around USB_CDC_SEND_ENCAPSULATED_COMMAND/
USB_CDC_GET_ENCAPSULATED_RESPONSE, ignoring the contents).  But this
would not allow multiplexing more than one client.   Therefore I added a
bit of QMI logic to allow multiple simultaneous clients, multiplexing by
the transaction ID.  This allows the wwan driver to act as a QMI client
and at the same time have multiple clients using the char device
interface.

I have a working demo of this, but the current state of the code is so
bad that I'm hesitating posting it publicly. It needs a lot of cleanup,
and I want to split out the remaining QMI parts from what's now become a
generic CDC encapsulated command/response interface.



Bjørn
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

From 250a8a1214ffb80bfc93e7d93f01796564dabd24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B8rn=20Mork?= <bjorn@mork.no>
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 <bjorn@mork.no>
---
 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;
 
 
+/* <linux/if.h> 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 <linux/module.h>
 #include <linux/netdevice.h>
+#include <linux/etherdevice.h>
 #include <linux/ethtool.h>
 #include <linux/mii.h>
 #include <linux/usb.h>
@@ -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