* Re: [PATCH] de2104x: remove experimental status
From: Jeff Garzik @ 2010-09-28 18:52 UTC (permalink / raw)
To: Ondrej Zary; +Cc: netdev, Kernel development list
In-Reply-To: <201009282046.21279.linux@rainbow-software.org>
On 09/28/2010 02:46 PM, Ondrej Zary wrote:
> It should be ready after 8 years...remove the experimental dependency.
>
> Signed-off-by: Ondrej Zary<linux@rainbow-software.org>
>
> --- linux-2.6.36-rc3-/drivers/net/tulip/Kconfig 2010-08-29 17:36:04.000000000 +0200
> +++ linux-2.6.36-rc3/drivers/net/tulip/Kconfig 2010-09-28 19:49:46.000000000 +0200
> @@ -11,8 +11,8 @@ menuconfig NET_TULIP
> if NET_TULIP
>
> config DE2104X
> - tristate "Early DECchip Tulip (dc2104x) PCI support (EXPERIMENTAL)"
> - depends on PCI&& EXPERIMENTAL
> + tristate "Early DECchip Tulip (dc2104x) PCI support"
> + depends on PCI
> select CRC32
> ---help---
> This driver is developed for the SMC EtherPower series Ethernet
Well... it's not the years, it's the quality... which I think has been
sufficiently increased.
Acked-by: Jeff Garzik <jgarzik@redhat.com>
^ permalink raw reply
* Re: [PATCH] de2104x: disable media debug messages by default
From: Jeff Garzik @ 2010-09-28 18:51 UTC (permalink / raw)
To: Ondrej Zary; +Cc: netdev, Kernel development list
In-Reply-To: <201009282018.57547.linux@rainbow-software.org>
On 09/28/2010 02:18 PM, Ondrej Zary wrote:
> Print media debug messages only when HW debug is enabled.
>
> Signed-off-by: Ondrej Zary<linux@rainbow-software.org>
>
> --- linux-2.6.36-rc3-/drivers/net/tulip/de2104x.c 2010-09-28 19:50:51.000000000 +0200
> +++ linux-2.6.36-rc3/drivers/net/tulip/de2104x.c 2010-09-28 20:05:34.000000000 +0200
> @@ -948,8 +948,9 @@ static void de_set_media (struct de_priv
> else
> macmode&= ~FullDuplex;
>
> - if (netif_msg_link(de)) {
> + if (netif_msg_link(de))
> dev_info(&de->dev->dev, "set link %s\n", media_name[media]);
> + if (netif_msg_hw(de)) {
> dev_info(&de->dev->dev, "mode 0x%x, sia 0x%x,0x%x,0x%x,0x%x\n",
> dr32(MacMode), dr32(SIAStatus),
> dr32(CSR13), dr32(CSR14), dr32(CSR15));
Acked-by: Jeff Garzik <jgarzik@redhat.com>
^ permalink raw reply
* WiMAX linux firmware pull request
From: Inaky Perez-Gonzalez @ 2010-09-28 18:46 UTC (permalink / raw)
To: David Woodhouse; +Cc: wimax, netdev
Hello David
I don't know if I forgot to send this request or it fell through the
cracks a fer kversions ago. Anyway, sending again.
Could you please pull
git://git.kernel.org/pub/scm/linux/kernel/git/inaky/linux-firmware-wimax.git
This contains the firmware for the 1.5 driver release for i5x50 and
i6x50 Intel cards, which have been available in
http://linuxwimax.org/Download since.
Thanks!
^ permalink raw reply
* Re: [PATCH v11 13/17] Add mp(mediate passthru) device.
From: Sridhar Samudrala @ 2010-09-28 18:48 UTC (permalink / raw)
To: Arnd Bergmann
Cc: Michael S. Tsirkin, Ben Hutchings, xiaohui.xin, netdev, kvm,
linux-kernel, mingo, davem, herbert, jdike
In-Reply-To: <201009281718.15813.arnd@arndb.de>
On 9/28/2010 8:18 AM, Arnd Bergmann wrote:
> On Tuesday 28 September 2010, Michael S. Tsirkin wrote:
>> On Tue, Sep 28, 2010 at 04:39:59PM +0200, Arnd Bergmann wrote:
>>> Can you be more specific what the problem is? Do you think
>>> it breaks when a guest sends VLAN tagged frames or when macvtap
>>> is connected to a VLAN interface that adds another tag (or
>>> only the combination)?
>> I expect the protocol value to be wrong when guest sends vlan tagged
>> frames as 802.1q frames have a different format.
> Ok, I see. Would that be fixed by using eth_type_trans()? I don't
> see any code in there that tries to deal with the VLAN tag, so
> do we have the same problem in the tun/tap driver?
tun_get_user() does call eth_type_trans(). Not sure why i didn't use it
in macvtap code.
Need to test it with guest VLAN tagging to make sure it works.
> Also, I wonder how we handle the case where both the guest and
> the host do VLAN tagging. Does the host transparently override
> the guest tag, or does it add a nested tag? More importantly,
> what should it do?
>
I would think If both guest and host do VLAN tagging, the tags will be
nested.
Thanks
Sridhar
^ permalink raw reply
* [PATCH] de2104x: remove experimental status
From: Ondrej Zary @ 2010-09-28 18:46 UTC (permalink / raw)
To: jgarzik; +Cc: netdev, Kernel development list
It should be ready after 8 years...remove the experimental dependency.
Signed-off-by: Ondrej Zary <linux@rainbow-software.org>
--- linux-2.6.36-rc3-/drivers/net/tulip/Kconfig 2010-08-29 17:36:04.000000000 +0200
+++ linux-2.6.36-rc3/drivers/net/tulip/Kconfig 2010-09-28 19:49:46.000000000 +0200
@@ -11,8 +11,8 @@ menuconfig NET_TULIP
if NET_TULIP
config DE2104X
- tristate "Early DECchip Tulip (dc2104x) PCI support (EXPERIMENTAL)"
- depends on PCI && EXPERIMENTAL
+ tristate "Early DECchip Tulip (dc2104x) PCI support"
+ depends on PCI
select CRC32
---help---
This driver is developed for the SMC EtherPower series Ethernet
--
Ondrej Zary
^ permalink raw reply
* Re: [RFC] Online firmware upgrade in non-embedded systems
From: Alexander Clouter @ 2010-09-28 18:45 UTC (permalink / raw)
To: linux-kernel; +Cc: netdev, linux-mtd
In-Reply-To: <1285696787.2282.45.camel@achroite.uk.solarflarecom.com>
In gmane.linux.kernel Ben Hutchings <bhutchings@solarflare.com> wrote:
>
> Currently the sfc network driver is optionally combined with an MTD
> driver (CONFIG_SFC_MTD) which exposes all upgradable firmware and
> configuration partitions in flash. This works nicely in kernels with
> MTD enabled, but since MTD is mainly used in embedded systems with
> on-board flash it is often disabled in distribution kernels and custom
> kernels alike. This leaves users of sfc unable to upgrade firmware
> without rebuilding the kernel or booting some other distribution. The
> lack of widespread MTD support is a regular cause of support requests.
>
----
ac56@proxy0:~$ uname -a
Linux proxy0 2.6.32-5-686 #1 SMP Wed Aug 25 14:28:12 UTC 2010 i686 GNU/Linux
ac56@proxy0:~$ cat /etc/debian_version
squeeze/sid
ac56@proxy0:~$ grep CONFIG_SFC_MTD /boot/config-2.6.32-5-686
CONFIG_SFC_MTD=y
ac56@proxy0:~$ grep '^CONFIG_MTD' /boot/config-2.6.32-5-686 | wc
72 72 1693
----
I tell them "use a real distribution".
Cheers
--
Alexander Clouter
.sigmonster says: BOFH excuse #119:
evil hackers from Serbia.
^ permalink raw reply
* Re: igmp: Allow mininum interval specification for igmp timers.
From: Christoph Lameter @ 2010-09-28 18:42 UTC (permalink / raw)
To: David Stevens
Cc: David Miller, linux-rdma-u79uwXL29TY76Z2rM5mHXA,
netdev-u79uwXL29TY76Z2rM5mHXA,
netdev-owner-u79uwXL29TY76Z2rM5mHXA, rda-x0S3BwdUo6DQT0dZR+AlfA
In-Reply-To: <OF4DDFA464.A933254C-ON882577AB.00754000-882577AB.00778C77-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
On Mon, 27 Sep 2010, David Stevens wrote:
> > The second patch sets the intervals to X .. X + Rand (interval) and not
> to
> > a table of fixed intervals as you state here. I have pointed this out
> > before.
>
> Sorry if I've misunderstood something you're proposing, but what
> you describe above would be certainly technically incorrect. There are
> really no circumstances for sending a report greater than <Interval>
> that is protocol-compliant. You can enforce a minimum greater than 0,
> which is a departure from both RFCs, though IGMPv2 uses wishy-washy
> language. The intent for both was to explicitly allow 0, IMO.
There is no igmp interval set by any igmp query yet so this is your usual
unresponsive crappy response to something else that we are not talking
about.
I thought you were talking about the "fixed intervals" that you saw in the
patch. These initial intervals are for unsolicited igmp reports (do I need
to add that statement to every sentence in a thread where we *only*
discuss unsolicited igmp issues?) and those "intervals" are randomized
and not fixed.
> > > as in the original patch. For v2, X=1 or 2 sec and Interval=10
> > > might work well, but for v3, the entire interval is 1 sec and I
> > > think I saw that the set-up time for the fabric may be on the
> > > order of 1 sec.
> >
> > Again there is no knowledge about V2 or V3 without a query and this is
> > during the period when no querier is known yet. You stated elsewhere
> that
> > I can assume V3 by default? So 1 sec?
>
> Yes, without a querier or the tunable to force it to IGMPv2,
> the default is IGMPv3. It appears there is a bug where IGMPv3 is also
> using a 10sec interval (haven't verified that), but a 1 sec interval
You do not have the linux source code tree available?
from net/ipv4/igmp.c:
#define IGMP_Unsolicited_Report_Interval (10*HZ)
> as required makes your situation worse, not better. It makes it even
> more likely that all the initial reports will occur before your set-up
> is done.
Right. So can you please give me an approach that considers all these
issues and does not invent problem that do not exist, stays within the
subject discussed and follows the RFCs?
> > There can be any number of reasons that a short outage could prevent the
> > packets from going through. A buffer overrun (that you mentioned
> > elsewhere) usually causes lots of packets to be lost. Buffer overrun
> > scenarios usually mean that all igmp queries are lost.
>
> You're arguing against protocol compliance. I didn't define
> the protocol, I only implemented it. And your view is through the
> IB lens, but I don't believe this is an actual problem in any way
> for typical networks. If you wrote a standards-track RFC that modifies
> IGMP for NBMA networks that require a delay or different parameters
> there, I'd have no objection to implementing that. Unilaterally
> changing linux's behavior on all network types without cause for
> departing from RFC on the most common types is another matter.
The RFCs state that the igmp queries have to be repeated at least 3
times. The first patch ensures that a mininum time passes between two igmp
reports (to avoid them getting lost in one go). The second patch doubles
the number of igmp reports and increases the intervals so that we still
have a chance to process the join before the next igmp query is send by
the router (which can be minuates away).
It fixes buggy havior that we see because multicast joins take very long
or fail outright.
> > There is no solution on the IB layer since there is no notification when
> > the fabric reconfiguration necessary for an multicast group is complete.
>
> Certainly that's not true; without notification, you can queue for
> first use of a new hardware multicast address and send the queue after an
> appropriate delay (1 sec? If that covers your set-up time). If you had
> positive acknowledgement from the IB network, you'd know exactly when to
> do it, but there's no need to change anything for non-IB networks here.
So you want an arbitrary delay for all new multicast traffic to be
created? I'd rather have a series of staggered attempts so that we can
avoid this setup time.
Also the setup time varies greatly depending on the complexity of the
fabric changes. It can be extremely fast if the multicast group is already
in use by others in the fabric. Adding a delay penalizes everyone
unnecessarily.
Also much of these seems to be contigent on IGMPv3. We are using igmpv2.
> > The querier is of not use since (for the gazillionth of times) this is
> an
> > unsolicited IGMP report. If there is a querier then the unsolicited igmp
> > reports would not be used but the timeout indicated by the querier would
> > be used.
>
> A querier affects unsolicited reports because it sets both the
> query interval and the robustness value. If you want to send 10 reports,
> you can cause that by having a querier that sets it to that many. The
> initial join would then send 10 reports and the query interval can also
> be as low as you like.
The Linux IGMP subsystem does not support either of those at this point.
When the multicast group is created it has no notion of query intervals
until the first igmp query is received. That is the period of interest
that we are discussing!
> But the linux code is not just for your particular problem or
> particular configuration. You can solve your problem by adding a querier,
> but I know you're trying to do it without. The mail I was responding to
> referred also to the case of a querier present, which is actually the
> "normal" case for using full IGMP is. I'm saying that for the non-querier
> case, making those per-interface configurable is reasonable because
> they *are* querier-changeable, but you can also use a querier to change
> it _for_the_unsolicited_reports_, as well as making the querier interval
> small enough that you don't have to care at all whether any or all of
> the unsolicited reports are lost.
The network of course has a querier that sents igmp requests in intervals
that could span minutes. We are talking about the period of time after a
join when a multicast group has been created but we have not reached the
time when the router sends an igmp query and where the various bits of
information about the igmp handling can be determined for the multicast
group.
I am not sure that you comprehend the basics of IGMP processing in the
kernel. I see knowledge about IGMP in general but you have a difficult
time to relate that to what the Linux kernel actually does.
Would you please have a look at the source code?
--
To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH] de2104x: disable media debug messages by default
From: Ondrej Zary @ 2010-09-28 18:18 UTC (permalink / raw)
To: jgarzik; +Cc: netdev, Kernel development list
Print media debug messages only when HW debug is enabled.
Signed-off-by: Ondrej Zary <linux@rainbow-software.org>
--- linux-2.6.36-rc3-/drivers/net/tulip/de2104x.c 2010-09-28 19:50:51.000000000 +0200
+++ linux-2.6.36-rc3/drivers/net/tulip/de2104x.c 2010-09-28 20:05:34.000000000 +0200
@@ -948,8 +948,9 @@ static void de_set_media (struct de_priv
else
macmode &= ~FullDuplex;
- if (netif_msg_link(de)) {
+ if (netif_msg_link(de))
dev_info(&de->dev->dev, "set link %s\n", media_name[media]);
+ if (netif_msg_hw(de)) {
dev_info(&de->dev->dev, "mode 0x%x, sia 0x%x,0x%x,0x%x,0x%x\n",
dr32(MacMode), dr32(SIAStatus),
dr32(CSR13), dr32(CSR14), dr32(CSR15));
--
Ondrej Zary
^ permalink raw reply
* [PATCH net-next 2.6] myri10ge: DCA update (resubmit)
From: Andrew Gallatin @ 2010-09-28 18:13 UTC (permalink / raw)
To: David Miller; +Cc: netdev, Loic Prylli
[-- Attachment #1: Type: text/plain, Size: 760 bytes --]
This patch contains the following DCA improvements to myri10ge:
1) Finally move myri10ge to use dca3 API
2) Disable PCIe relaxed ordering when enabling DCA on
myri10ge. This provides a performance boost on Nehalem
based Xeons
3) Make sure to properly initialize NIC's DCA state when it is enabled,
rather than giving the NIC a bogus tag (0) and waiting for
the first received packet to trigger an update. Not using a
real tag can cause hardware exceptions on some motherboards
when a CPU socket is empty.
3) Always update the cached CPU when our interrupt affinity changes
so as to avoid excessive calls to dca3_get_tag()
Signed-off-by: Andrew Gallatin <gallatin@myri.com>
Signed-off-by: Loic Prylli <loic@myri.com>
[-- Attachment #2: myri10ge_dca_2.diff --]
[-- Type: text/x-diff, Size: 2216 bytes --]
diff --git a/drivers/net/myri10ge/myri10ge.c b/drivers/net/myri10ge/myri10ge.c
index 4f3a3c0..02dd92e 100644
--- a/drivers/net/myri10ge/myri10ge.c
+++ b/drivers/net/myri10ge/myri10ge.c
@@ -225,6 +225,7 @@ struct myri10ge_priv {
struct msix_entry *msix_vectors;
#ifdef CONFIG_MYRI10GE_DCA
int dca_enabled;
+ int relaxed_order;
#endif
u32 link_state;
unsigned int rdma_tags_available;
@@ -1074,10 +1075,28 @@ static int myri10ge_reset(struct myri10ge_priv *mgp)
}
#ifdef CONFIG_MYRI10GE_DCA
+static int myri10ge_toggle_relaxed(struct pci_dev *pdev, int on)
+{
+ int ret, cap, err;
+ u16 ctl;
+
+ cap = pci_find_capability(pdev, PCI_CAP_ID_EXP);
+ if (!cap)
+ return 0;
+
+ err = pci_read_config_word(pdev, cap + PCI_EXP_DEVCTL, &ctl);
+ ret = (ctl & PCI_EXP_DEVCTL_RELAX_EN) >> 4;
+ if (ret != on) {
+ ctl &= ~PCI_EXP_DEVCTL_RELAX_EN;
+ ctl |= (on << 4);
+ pci_write_config_word(pdev, cap + PCI_EXP_DEVCTL, ctl);
+ }
+ return ret;
+}
+
static void
myri10ge_write_dca(struct myri10ge_slice_state *ss, int cpu, int tag)
{
- ss->cpu = cpu;
ss->cached_dca_tag = tag;
put_be32(htonl(tag), ss->dca_tag);
}
@@ -1088,9 +1107,10 @@ static inline void myri10ge_update_dca(struct myri10ge_slice_state *ss)
int tag;
if (cpu != ss->cpu) {
- tag = dca_get_tag(cpu);
+ tag = dca3_get_tag(&ss->mgp->pdev->dev, cpu);
if (ss->cached_dca_tag != tag)
myri10ge_write_dca(ss, cpu, tag);
+ ss->cpu = cpu;
}
put_cpu();
}
@@ -1113,9 +1133,13 @@ static void myri10ge_setup_dca(struct myri10ge_priv *mgp)
"dca_add_requester() failed, err=%d\n", err);
return;
}
+ mgp->relaxed_order = myri10ge_toggle_relaxed(pdev, 0);
mgp->dca_enabled = 1;
- for (i = 0; i < mgp->num_slices; i++)
- myri10ge_write_dca(&mgp->ss[i], -1, 0);
+ for (i = 0; i < mgp->num_slices; i++) {
+ mgp->ss[i].cpu = -1;
+ mgp->ss[i].cached_dca_tag = -1;
+ myri10ge_update_dca(&mgp->ss[i]);
+ }
}
static void myri10ge_teardown_dca(struct myri10ge_priv *mgp)
@@ -1126,6 +1150,8 @@ static void myri10ge_teardown_dca(struct myri10ge_priv *mgp)
if (!mgp->dca_enabled)
return;
mgp->dca_enabled = 0;
+ if (mgp->relaxed_order)
+ myri10ge_toggle_relaxed(pdev, 1);
err = dca_remove_requester(&pdev->dev);
}
^ permalink raw reply related
* Re: [PATCH] Add Qualcomm Gobi 2000 driver.
From: Joe Perches @ 2010-09-28 18:11 UTC (permalink / raw)
To: Elly Jones; +Cc: netdev, dbrownell, mjg59, jglasgow, msb, olofj
In-Reply-To: <20100928171026.GB6083@google.com>
On Tue, 2010-09-28 at 13:10 -0400, Elly Jones wrote:
> From: Elizabeth Jones <ellyjones@google.com>
Hi.
The readme is out of date and should be updated.
[]
> +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,23)
> + usbnet = usb_get_intfdata(iface);
> +#else
> + usbnet = iface->dev.platform_data;
> +#endif
I think it's not particularly useful to include support
for versions < current when this driver is added.
[]
> +#define DBG(format, arg...) \
> + if (debug == 1) { \
> + printk(KERN_INFO "QCUSBNet2k::%s " format, __FUNCTION__, ## arg); \
> + }
This should be surrounded by a do {} while(0)
#define DBG(fmt, arg...) \
do { \
if (debug == 1) \
printk(KERN_INFO "QCUSBNet2k::%s " fmt, __func__, ##arg); \
} while (0)
I think many of these DBG messages should
not use DBG but the more normal pr_<level>,
dev_<level> or netdev_<level> so generally
useful messages are logged without getting
all the debugging messages.
^ permalink raw reply
* Re: [IPoIB] Identify multicast packets and fix IGMP breakage V3
From: Roland Dreier @ 2010-09-28 18:09 UTC (permalink / raw)
To: Christoph Lameter
Cc: Jason Gunthorpe, linux-rdma-u79uwXL29TY76Z2rM5mHXA,
ogerlitz-smomgflXvOZWk0Htik3J/w, yosefe-smomgflXvOZWk0Htik3J/w,
netdev-u79uwXL29TY76Z2rM5mHXA, David Miller
In-Reply-To: <alpine.DEB.2.00.1008270827280.11792-sBS69tsa9Uj/9pzu0YdTqQ@public.gmane.org>
thanks, applied.
--
To unsubscribe from this list: send the line "unsubscribe linux-rdma" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH net-next2.6] net: rename netdev rx_queue to ingress_queue
From: Jarek Poplawski @ 2010-09-28 18:04 UTC (permalink / raw)
To: Eric Dumazet; +Cc: jamal, David Miller, netdev
In-Reply-To: <1285689517.3154.76.camel@edumazet-laptop>
On Tue, Sep 28, 2010 at 05:58:37PM +0200, Eric Dumazet wrote:
> There is some confusion with rx_queue name after RPS, and net drivers
> private rx_queue fields.
>
> I suggest to rename "struct net_device"->rx_queue to ingress_queue.
>
> Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
> ---
>
> My plan is to make this structure dynamically allocated, in the rare
> case ingress is setup, to shrink net_device.
I hope Master of Ingress will forgive you this CC some day... ;-)
Jarek P.
>
> include/linux/netdevice.h | 2 +-
> net/core/dev.c | 8 ++++----
> net/sched/sch_api.c | 14 +++++++-------
> net/sched/sch_generic.c | 8 ++++----
> 4 files changed, 16 insertions(+), 16 deletions(-)
>
> diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
> index 83de0eb..1ae0d65 100644
> --- a/include/linux/netdevice.h
> +++ b/include/linux/netdevice.h
> @@ -983,7 +983,7 @@ struct net_device {
> rx_handler_func_t *rx_handler;
> void *rx_handler_data;
>
> - struct netdev_queue rx_queue; /* use two cache lines */
> + struct netdev_queue ingress_queue; /* use two cache lines */
>
> /*
> * Cache lines mostly used on transmit path
> diff --git a/net/core/dev.c b/net/core/dev.c
> index 42b200f..0ed0669 100644
> --- a/net/core/dev.c
> +++ b/net/core/dev.c
> @@ -2674,7 +2674,7 @@ static int ing_filter(struct sk_buff *skb)
> skb->tc_verd = SET_TC_RTTL(skb->tc_verd, ttl);
> skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_INGRESS);
>
> - rxq = &dev->rx_queue;
> + rxq = &dev->ingress_queue;
>
> q = rxq->qdisc;
> if (q != &noop_qdisc) {
> @@ -2691,7 +2691,7 @@ static inline struct sk_buff *handle_ing(struct sk_buff *skb,
> struct packet_type **pt_prev,
> int *ret, struct net_device *orig_dev)
> {
> - if (skb->dev->rx_queue.qdisc == &noop_qdisc)
> + if (skb->dev->ingress_queue.qdisc == &noop_qdisc)
> goto out;
>
> if (*pt_prev) {
> @@ -4894,7 +4894,7 @@ static void __netdev_init_queue_locks_one(struct net_device *dev,
> static void netdev_init_queue_locks(struct net_device *dev)
> {
> netdev_for_each_tx_queue(dev, __netdev_init_queue_locks_one, NULL);
> - __netdev_init_queue_locks_one(dev, &dev->rx_queue, NULL);
> + __netdev_init_queue_locks_one(dev, &dev->ingress_queue, NULL);
> }
>
> unsigned long netdev_fix_features(unsigned long features, const char *name)
> @@ -5406,7 +5406,7 @@ static void netdev_init_one_queue(struct net_device *dev,
>
> static void netdev_init_queues(struct net_device *dev)
> {
> - netdev_init_one_queue(dev, &dev->rx_queue, NULL);
> + netdev_init_one_queue(dev, &dev->ingress_queue, NULL);
> netdev_for_each_tx_queue(dev, netdev_init_one_queue, NULL);
> spin_lock_init(&dev->tx_global_lock);
> }
> diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c
> index 6fb3d41..b802078 100644
> --- a/net/sched/sch_api.c
> +++ b/net/sched/sch_api.c
> @@ -240,7 +240,7 @@ struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle)
> if (q)
> goto out;
>
> - q = qdisc_match_from_root(dev->rx_queue.qdisc_sleeping, handle);
> + q = qdisc_match_from_root(dev->ingress_queue.qdisc_sleeping, handle);
> out:
> return q;
> }
> @@ -701,7 +701,7 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
> }
>
> for (i = 0; i < num_q; i++) {
> - struct netdev_queue *dev_queue = &dev->rx_queue;
> + struct netdev_queue *dev_queue = &dev->ingress_queue;
>
> if (!ingress)
> dev_queue = netdev_get_tx_queue(dev, i);
> @@ -979,7 +979,7 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
> return -ENOENT;
> q = qdisc_leaf(p, clid);
> } else { /* ingress */
> - q = dev->rx_queue.qdisc_sleeping;
> + q = dev->ingress_queue.qdisc_sleeping;
> }
> } else {
> q = dev->qdisc;
> @@ -1044,7 +1044,7 @@ replay:
> return -ENOENT;
> q = qdisc_leaf(p, clid);
> } else { /*ingress */
> - q = dev->rx_queue.qdisc_sleeping;
> + q = dev->ingress_queue.qdisc_sleeping;
> }
> } else {
> q = dev->qdisc;
> @@ -1124,7 +1124,7 @@ create_n_graft:
> if (!(n->nlmsg_flags&NLM_F_CREATE))
> return -ENOENT;
> if (clid == TC_H_INGRESS)
> - q = qdisc_create(dev, &dev->rx_queue, p,
> + q = qdisc_create(dev, &dev->ingress_queue, p,
> tcm->tcm_parent, tcm->tcm_parent,
> tca, &err);
> else {
> @@ -1304,7 +1304,7 @@ static int tc_dump_qdisc(struct sk_buff *skb, struct netlink_callback *cb)
> if (tc_dump_qdisc_root(dev->qdisc, skb, cb, &q_idx, s_q_idx) < 0)
> goto done;
>
> - dev_queue = &dev->rx_queue;
> + dev_queue = &dev->ingress_queue;
> if (tc_dump_qdisc_root(dev_queue->qdisc_sleeping, skb, cb, &q_idx, s_q_idx) < 0)
> goto done;
>
> @@ -1595,7 +1595,7 @@ static int tc_dump_tclass(struct sk_buff *skb, struct netlink_callback *cb)
> if (tc_dump_tclass_root(dev->qdisc, skb, tcm, cb, &t, s_t) < 0)
> goto done;
>
> - dev_queue = &dev->rx_queue;
> + dev_queue = &dev->ingress_queue;
> if (tc_dump_tclass_root(dev_queue->qdisc_sleeping, skb, tcm, cb, &t, s_t) < 0)
> goto done;
>
> diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c
> index 2aeb3a4..545278a 100644
> --- a/net/sched/sch_generic.c
> +++ b/net/sched/sch_generic.c
> @@ -753,7 +753,7 @@ void dev_activate(struct net_device *dev)
>
> need_watchdog = 0;
> netdev_for_each_tx_queue(dev, transition_one_qdisc, &need_watchdog);
> - transition_one_qdisc(dev, &dev->rx_queue, NULL);
> + transition_one_qdisc(dev, &dev->ingress_queue, NULL);
>
> if (need_watchdog) {
> dev->trans_start = jiffies;
> @@ -812,7 +812,7 @@ static bool some_qdisc_is_busy(struct net_device *dev)
> void dev_deactivate(struct net_device *dev)
> {
> netdev_for_each_tx_queue(dev, dev_deactivate_queue, &noop_qdisc);
> - dev_deactivate_queue(dev, &dev->rx_queue, &noop_qdisc);
> + dev_deactivate_queue(dev, &dev->ingress_queue, &noop_qdisc);
>
> dev_watchdog_down(dev);
>
> @@ -838,7 +838,7 @@ void dev_init_scheduler(struct net_device *dev)
> {
> dev->qdisc = &noop_qdisc;
> netdev_for_each_tx_queue(dev, dev_init_scheduler_queue, &noop_qdisc);
> - dev_init_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);
> + dev_init_scheduler_queue(dev, &dev->ingress_queue, &noop_qdisc);
>
> setup_timer(&dev->watchdog_timer, dev_watchdog, (unsigned long)dev);
> }
> @@ -861,7 +861,7 @@ static void shutdown_scheduler_queue(struct net_device *dev,
> void dev_shutdown(struct net_device *dev)
> {
> netdev_for_each_tx_queue(dev, shutdown_scheduler_queue, &noop_qdisc);
> - shutdown_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);
> + shutdown_scheduler_queue(dev, &dev->ingress_queue, &noop_qdisc);
> qdisc_destroy(dev->qdisc);
> dev->qdisc = &noop_qdisc;
>
>
>
^ permalink raw reply
* [RFC] Online firmware upgrade in non-embedded systems
From: Ben Hutchings @ 2010-09-28 17:59 UTC (permalink / raw)
To: netdev, linux-kernel, linux-mtd; +Cc: sf-linux-drivers
Network and disk controllers normally have at least some firmware in
flash to support their use as boot devices. Solarflare's current
network adapters can have as many as 4 distinct firmware partitions: one
for each PHY, one for the management CPU and one for net-boot. Any or
all of these may need to be updated in the field, to fix bugs or to add
features. (Furthermore, the net-boot firmware is based on gPXE and is
therefore modifiable by any user.)
Currently the sfc network driver is optionally combined with an MTD
driver (CONFIG_SFC_MTD) which exposes all upgradable firmware and
configuration partitions in flash. This works nicely in kernels with
MTD enabled, but since MTD is mainly used in embedded systems with
on-board flash it is often disabled in distribution kernels and custom
kernels alike. This leaves users of sfc unable to upgrade firmware
without rebuilding the kernel or booting some other distribution. The
lack of widespread MTD support is a regular cause of support requests.
There are two main alternatives I'm aware of:
- Use the ethtool ETHTOOL_SEEPROM command with multiple magic numbers
for different partitions. bnx2x does something a little like this for
PHY firmware upgrades. The ethtool core passes in a page at a time so
this won't work well with flash block sizes greater than PAGE_SIZE.
- Use the ethtool ETHTOOL_FLASHDEV command. It's intended for precisely
this purpose, but the driver is supposed to implement it using
request_firmware(). That seems to require any firmware update program to
integrate with udev or to copy firmware images under /lib/firmware even
though they are one-time updates. That doesn't make a lot of sense to
me. Am I missing something?
Of course these are both specific to network devices; it seems deisrable
to have a more general convention for online firmware upgrades. MTDs
clearly are more generally applicable, and pretty much every computer
does have flash storage for firmware and boot configuration, so perhaps
it should be treated as more of a standard feature?
Ben.
--
Ben Hutchings, Senior Software Engineer, Solarflare Communications
Not speaking for my employer; that's the marketing department's job.
They asked us to note that Solarflare product names are trademarked.
^ permalink raw reply
* Re: [PATCH] ipv4: remove all rt cache entries on UNREGISTER event
From: Octavian Purdila @ 2010-09-28 17:35 UTC (permalink / raw)
To: nicolas.dichtel; +Cc: Eric Dumazet, netdev
In-Reply-To: <1285691629.3154.80.camel@edumazet-laptop>
From: Eric Dumazet <eric.dumazet@gmail.com>
Date: Tuesday 28 September 2010, 19:33:49
> Le mardi 28 septembre 2010 à 17:24 +0200, Nicolas Dichtel a écrit :
> > Hi,
> >
> > I face a problem when I try to remove an interface,
> > netdev_wait_allrefs() complains about refcount.
> >
> > Here is a trivial scenario to reproduce the problem:
> > # ip tunnel add mode ipip remote 10.16.0.164 local 10.16.0.72 dev eth0
> > # ./a.out tunl1
> > # ip tunnel del tunl1
> >
> > Note: a.out binary create an IPv4 raw socket, attach it to tunl1
> > (SO_BINDTODEVICE), set it as multicast (IP_MULTICAST_LOOP), set the
> > multicast interface to tunl1 (IP_MULTICAST_IF), build the IP header
> > (IP_HDRINCL) and then send a single packet (192.168.6.1 -> 224.0.0.18).
> >
> > Note2: when a.out is executed, tunl1 has no ip address and is down.
>
> CC Octavian Purdila, the patch author.
>
> I am just wondering why this route is created in the first place.
>
> Maybe a fix would be to forbid this ?
>
> Some machines have a giant route cache, so its very important to avoid
> expensive scans.
>
> > Then, I got a serie of "kernel:[1206699.728010] unregister_netdevice:
> > waiting for tunl1 to become free. Usage count = 3" and after some time,
> > interface is removed.
> >
> > The problem is that route cache entries are only invalidate on
> > UNREGISTER event, and not removed (introduced by commit
> > e2ce146848c81af2f6d42e67990191c284bf0c33). We must wait that
> > rt_check_expire() remove the remaining route cache entries.
> >
> > To fix the problem, I propose to remove a part of the previous commit.
> >
Hi Nicolas,
The purpose of my original patch was to speed up interfaces deregistration
even more after Eric's batch work. Reverting it might slow things down again,
but since this is breaking things we should probably revert it and think a
proper optimization afterward. I know that Eric B has done some more work in
this area, for batch namespace cleanup, maybe the issue is not even there
anymore.
So, Ack from me.
We might even fully revert the patch, since the bit that is left doesn't have
any value anymore.
Thanks,
tavi
^ permalink raw reply
* Re: [PATCH net-next 2.6] myri10ge: DCA update
From: David Miller @ 2010-09-28 17:40 UTC (permalink / raw)
To: gallatin; +Cc: eric.dumazet, netdev, loic
In-Reply-To: <4CA202FC.5000609@myri.com>
From: Andrew Gallatin <gallatin@myri.com>
Date: Tue, 28 Sep 2010 11:00:12 -0400
> I've corrected the style problems pointed out by checkpatch, and
> I've attached a new diff. Is that sufficient, or do I start a
> new thread, or..?
Please send a new fresh patch submission, or as you term it
"start a new thread" :-)
^ permalink raw reply
* Re: [PATCH net-next-2.6 04/17] bnx2x: Use netif_set_real_num_{rx,tx}_queues()
From: David Miller @ 2010-09-28 17:39 UTC (permalink / raw)
To: vladz; +Cc: bhutchings, netdev, linux-net-drivers, eilong
In-Reply-To: <8628FE4E7912BF47A96AE7DD7BAC0AADDDEDAE6ECA@SJEXCHCCR02.corp.ad.broadcom.com>
From: "Vladislav Zolotarov" <vladz@broadcom.com>
Date: Tue, 28 Sep 2010 05:59:15 -0700
> Ben, I'm going to fix the bnx2x the way your patch would apply. I
> suspect this would probably require u to respin the bnx2x patch.
> Sorry for the inconvenience.
Ben's patch is already in the tree, so you need to send me patches
relative to his.
^ permalink raw reply
* Re: [PATCH -next] pch_gbe: add header files
From: David Miller @ 2010-09-28 17:34 UTC (permalink / raw)
To: randy.dunlap; +Cc: sfr, netdev, linux-next, linux-kernel, masa-korg
In-Reply-To: <20100928095643.18c5020f.randy.dunlap@oracle.com>
From: Randy Dunlap <randy.dunlap@oracle.com>
Date: Tue, 28 Sep 2010 09:56:43 -0700
> From: Randy Dunlap <randy.dunlap@oracle.com>
>
> Fix build errors, more header files needed.
>
> drivers/net/pch_gbe/pch_gbe_main.c:965: error: implicit declaration of function 'tcp_hdr'
> drivers/net/pch_gbe/pch_gbe_main.c:965: error: invalid type argument of '->' (have 'int')
> drivers/net/pch_gbe/pch_gbe_main.c:968: error: invalid type argument of '->' (have 'int')
> drivers/net/pch_gbe/pch_gbe_main.c:976: error: implicit declaration of function 'udp_hdr'
> drivers/net/pch_gbe/pch_gbe_main.c:976: error: invalid type argument of '->' (have 'int')
> drivers/net/pch_gbe/pch_gbe_main.c:980: error: invalid type argument of '->' (have 'int')
>
> Signed-off-by: Randy Dunlap <randy.dunlap@oracle.com>
Applied, thanks Randy.
^ permalink raw reply
* Re: [PATCH] Add Qualcomm Gobi 2000 driver.
From: Ben Hutchings @ 2010-09-28 17:16 UTC (permalink / raw)
To: Elly Jones; +Cc: netdev, dbrownell, mjg59, jglasgow, msb, olofj
In-Reply-To: <20100928171026.GB6083@google.com>
On Tue, 2010-09-28 at 13:10 -0400, Elly Jones wrote:
> From: Elizabeth Jones <ellyjones@google.com>
>
> This driver is a rewrite of the original Qualcomm GPL driver, released as part
> of Qualcomm's "Code Aurora" initiative. The driver has been transformed into
> Linux kernel style and made to use kernel APIs where appropriate; some bugs have
> also been fixed. Note that the device in question requires firmware and a
> firmware loader; the latter has been written by mjg (see
> http://www.codon.org.uk/~mjg59/gobi_loader/).
[...]
You should run this through checkpatch.pl and fix the style errors it
highlights.
Ben.
--
Ben Hutchings, Senior Software Engineer, Solarflare Communications
Not speaking for my employer; that's the marketing department's job.
They asked us to note that Solarflare product names are trademarked.
^ permalink raw reply
* [PATCH] Add Qualcomm Gobi 2000 driver.
From: Elly Jones @ 2010-09-28 17:10 UTC (permalink / raw)
To: netdev; +Cc: dbrownell, mjg59, jglasgow, msb, olofj
From: Elizabeth Jones <ellyjones@google.com>
This driver is a rewrite of the original Qualcomm GPL driver, released as part
of Qualcomm's "Code Aurora" initiative. The driver has been transformed into
Linux kernel style and made to use kernel APIs where appropriate; some bugs have
also been fixed. Note that the device in question requires firmware and a
firmware loader; the latter has been written by mjg (see
http://www.codon.org.uk/~mjg59/gobi_loader/).
Signed-Off-By: Elizabeth Jones <ellyjones@google.com>
Signed-Off-By: Jason Glasgow <jglasgow@google.com>
---
drivers/net/usb/Kconfig | 6 +
drivers/net/usb/Makefile | 2 +-
drivers/net/usb/qcusbnet/Makefile | 2 +
drivers/net/usb/qcusbnet/qcusbnet.c | 706 +++++++++++++++
drivers/net/usb/qcusbnet/qcusbnet.h | 24 +
drivers/net/usb/qcusbnet/qmi.c | 358 ++++++++
drivers/net/usb/qcusbnet/qmi.h | 67 ++
drivers/net/usb/qcusbnet/qmidevice.c | 1621 ++++++++++++++++++++++++++++++++++
drivers/net/usb/qcusbnet/qmidevice.h | 36 +
drivers/net/usb/qcusbnet/readme.txt | 118 +++
drivers/net/usb/qcusbnet/structs.h | 99 ++
11 files changed, 3038 insertions(+), 1 deletions(-)
create mode 100755 drivers/net/usb/qcusbnet/Makefile
create mode 100644 drivers/net/usb/qcusbnet/qcusbnet.c
create mode 100644 drivers/net/usb/qcusbnet/qcusbnet.h
create mode 100755 drivers/net/usb/qcusbnet/qmi.c
create mode 100755 drivers/net/usb/qcusbnet/qmi.h
create mode 100755 drivers/net/usb/qcusbnet/qmidevice.c
create mode 100755 drivers/net/usb/qcusbnet/qmidevice.h
create mode 100755 drivers/net/usb/qcusbnet/readme.txt
create mode 100755 drivers/net/usb/qcusbnet/structs.h
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index d7b7018..8a210d9 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -406,4 +406,10 @@ config USB_SIERRA_NET
To compile this driver as a module, choose M here: the
module will be called sierra_net.
+config USB_NET_GOBI
+ tristate "Qualcomm Gobi"
+ depends on USB_USBNET
+ help
+ Choose this option if you have a Qualcomm Gobi 2000 cellular modem.
+
endmenu
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index b13a279..f3f702f 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -25,4 +25,4 @@ obj-$(CONFIG_USB_NET_INT51X1) += int51x1.o
obj-$(CONFIG_USB_CDC_PHONET) += cdc-phonet.o
obj-$(CONFIG_USB_IPHETH) += ipheth.o
obj-$(CONFIG_USB_SIERRA_NET) += sierra_net.o
-
+obj-$(CONFIG_USB_NET_GOBI) += qcusbnet/
diff --git a/drivers/net/usb/qcusbnet/Makefile b/drivers/net/usb/qcusbnet/Makefile
new file mode 100755
index 0000000..a186c19
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_USB_NET_GOBI) += qcusbnet2k.o
+qcusbnet2k-objs += qcusbnet.o qmidevice.o qmi.o
diff --git a/drivers/net/usb/qcusbnet/qcusbnet.c b/drivers/net/usb/qcusbnet/qcusbnet.c
new file mode 100644
index 0000000..a075b55
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/qcusbnet.c
@@ -0,0 +1,706 @@
+/* qcusbnet.c - gobi network device
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "structs.h"
+#include "qmidevice.h"
+#include "qmi.h"
+#include "qcusbnet.h"
+
+#define DRIVER_VERSION "1.0.110"
+#define DRIVER_AUTHOR "Qualcomm Innovation Center"
+#define DRIVER_DESC "QCUSBNet2k"
+
+int debug;
+static struct class *devclass;
+
+int qc_suspend(struct usb_interface *iface, pm_message_t event)
+{
+ struct usbnet * usbnet;
+ struct qcusbnet * dev;
+
+ if (!iface)
+ return -ENOMEM;
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,23)
+ usbnet = usb_get_intfdata(iface);
+#else
+ usbnet = iface->dev.platform_data;
+#endif
+
+ if (!usbnet || !usbnet->net) {
+ DBG("failed to get netdevice\n");
+ return -ENXIO;
+ }
+
+ dev = (struct qcusbnet *)usbnet->data[0];
+ if (!dev) {
+ DBG("failed to get QMIDevice\n");
+ return -ENXIO;
+ }
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+ if (!usbnet->udev->auto_pm)
+#else
+ if (!(event.event & PM_EVENT_AUTO))
+#endif
+ {
+ DBG("device suspended to power level %d\n",
+ event.event);
+ qc_setdown(dev, DOWN_DRIVER_SUSPENDED);
+ } else {
+ DBG("device autosuspend\n");
+ }
+
+ if (event.event & PM_EVENT_SUSPEND) {
+ qc_stopread(dev);
+ usbnet->udev->reset_resume = 0;
+ iface->dev.power.power_state.event = event.event;
+ } else {
+ usbnet->udev->reset_resume = 1;
+ }
+
+ return usbnet_suspend(iface, event);
+}
+
+static int qc_resume(struct usb_interface * iface)
+{
+ struct usbnet *usbnet;
+ struct qcusbnet *dev;
+ int ret;
+ int oldstate;
+
+ if (iface == 0)
+ return -ENOMEM;
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,23)
+ usbnet = usb_get_intfdata(iface);
+#else
+ usbnet = iface->dev.platform_data;
+#endif
+
+ if (!usbnet || !usbnet->net) {
+ DBG("failed to get netdevice\n");
+ return -ENXIO;
+ }
+
+ dev = (struct qcusbnet *)usbnet->data[0];
+ if (!dev) {
+ DBG("failed to get QMIDevice\n");
+ return -ENXIO;
+ }
+
+ oldstate = iface->dev.power.power_state.event;
+ iface->dev.power.power_state.event = PM_EVENT_ON;
+ DBG("resuming from power mode %d\n", oldstate);
+
+ if (oldstate & PM_EVENT_SUSPEND) {
+ qc_cleardown(dev, DOWN_DRIVER_SUSPENDED);
+
+ ret = usbnet_resume(iface);
+ if (ret) {
+ DBG("usbnet_resume error %d\n", ret);
+ return ret;
+ }
+
+ ret = qc_startread(dev);
+ if (ret) {
+ DBG("qc_startread error %d\n", ret);
+ return ret;
+ }
+
+ complete(&dev->worker.work);
+ } else {
+ DBG("nothing to resume\n");
+ return 0;
+ }
+
+ return ret;
+}
+
+static int qcnet_bind(struct usbnet *usbnet, struct usb_interface *iface)
+{
+ int numends;
+ int i;
+ struct usb_host_endpoint *endpoint = NULL;
+ struct usb_host_endpoint *in = NULL;
+ struct usb_host_endpoint *out = NULL;
+
+ if (iface->num_altsetting != 1) {
+ DBG("invalid num_altsetting %u\n", iface->num_altsetting);
+ return -EINVAL;
+ }
+
+ if (iface->cur_altsetting->desc.bInterfaceNumber != 0) {
+ DBG("invalid interface %d\n",
+ iface->cur_altsetting->desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ numends = iface->cur_altsetting->desc.bNumEndpoints;
+ for (i = 0; i < numends; i++) {
+ endpoint = iface->cur_altsetting->endpoint + i;
+ if (!endpoint) {
+ DBG("invalid endpoint %u\n", i);
+ return -EINVAL;
+ }
+
+ if (usb_endpoint_dir_in(&endpoint->desc)
+ && !usb_endpoint_xfer_int(&endpoint->desc)) {
+ in = endpoint;
+ } else if (!usb_endpoint_dir_out(&endpoint->desc)) {
+ out = endpoint;
+ }
+ }
+
+ if (!in || !out) {
+ DBG("invalid endpoints\n");
+ return -EINVAL;
+ }
+
+ if (usb_set_interface(usbnet->udev,
+ iface->cur_altsetting->desc.bInterfaceNumber, 0)) {
+ DBG("unable to set interface\n");
+ return -EINVAL;
+ }
+
+ usbnet->in = usb_rcvbulkpipe(usbnet->udev, in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+ usbnet->out = usb_sndbulkpipe(usbnet->udev, out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+
+ DBG("in %x, out %x\n",
+ in->desc.bEndpointAddress,
+ out->desc.bEndpointAddress);
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,23)
+ iface->dev.platform_data = usbnet;
+#endif
+ return 0;
+}
+
+static void qcnet_unbind(struct usbnet *usbnet, struct usb_interface *iface)
+{
+ struct qcusbnet *dev = (struct qcusbnet *)usbnet->data[0];
+
+ netif_carrier_off(usbnet->net);
+ qc_deregister(dev);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)
+ kfree(usbnet->net->netdev_ops);
+ usbnet->net->netdev_ops = NULL;
+#endif
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,23)
+ iface->dev.platform_data = NULL;
+#endif
+
+ kfree(dev);
+}
+
+static void qcnet_urbhook(struct urb * urb)
+{
+ unsigned long flags;
+ struct worker * worker = urb->context;
+ if (!worker) {
+ DBG("bad context\n");
+ return;
+ }
+
+ if (urb->status) {
+ DBG("urb finished with error %d\n", urb->status);
+ }
+
+ spin_lock_irqsave(&worker->active_lock, flags);
+ worker->active = ERR_PTR(-EAGAIN);
+ spin_unlock_irqrestore(&worker->active_lock, flags);
+ /* XXX-fix race against qcnet_stop()? */
+ complete(&worker->work);
+ usb_free_urb(urb);
+}
+
+static void qcnet_txtimeout(struct net_device *netdev)
+{
+ struct list_head *node, *tmp;
+ struct qcusbnet *dev;
+ struct worker *worker;
+ struct urbreq *req;
+ unsigned long activeflags, listflags;
+ struct usbnet *usbnet = netdev_priv(netdev);
+
+ if (!usbnet || !usbnet->net) {
+ DBG("failed to get usbnet device\n");
+ return;
+ }
+
+ dev = (struct qcusbnet *)usbnet->data[0];
+ if (!dev) {
+ DBG("failed to get QMIDevice\n");
+ return;
+ }
+ worker = &dev->worker;
+
+ DBG("\n");
+
+ spin_lock_irqsave(&worker->active_lock, activeflags);
+ if (worker->active)
+ usb_kill_urb(worker->active);
+ spin_unlock_irqrestore(&worker->active_lock, activeflags);
+
+ spin_lock_irqsave(&worker->urbs_lock, listflags);
+ list_for_each_safe(node, tmp, &worker->urbs) {
+ req = list_entry(node, struct urbreq, node);
+ usb_free_urb(req->urb);
+ list_del(&req->node);
+ kfree(req);
+ }
+ spin_unlock_irqrestore(&worker->urbs_lock, listflags);
+
+ complete(&worker->work);
+}
+
+static int qcnet_worker(void *arg)
+{
+ struct list_head *node, *tmp;
+ unsigned long activeflags, listflags;
+ struct urbreq *req;
+ int status;
+ struct usb_device *usbdev;
+ struct worker *worker = arg;
+ if (!worker) {
+ DBG("passed null pointer\n");
+ return -EINVAL;
+ }
+
+ usbdev = interface_to_usbdev(worker->iface);
+
+ DBG("traffic thread started\n");
+
+ while (!kthread_should_stop()) {
+ wait_for_completion_interruptible(&worker->work);
+
+ if (kthread_should_stop()) {
+ spin_lock_irqsave(&worker->active_lock, activeflags);
+ if (worker->active) {
+ usb_kill_urb(worker->active);
+ }
+ spin_unlock_irqrestore(&worker->active_lock, activeflags);
+
+ spin_lock_irqsave(&worker->urbs_lock, listflags);
+ list_for_each_safe(node, tmp, &worker->urbs) {
+ req = list_entry(node, struct urbreq, node);
+ usb_free_urb(req->urb);
+ list_del(&req->node);
+ kfree(req);
+ }
+ spin_unlock_irqrestore(&worker->urbs_lock, listflags);
+
+ break;
+ }
+
+ spin_lock_irqsave(&worker->active_lock, activeflags);
+ if (IS_ERR(worker->active) && PTR_ERR(worker->active) == -EAGAIN) {
+ worker->active = NULL;
+ spin_unlock_irqrestore(&worker->active_lock, activeflags);
+ usb_autopm_put_interface(worker->iface);
+ spin_lock_irqsave(&worker->active_lock, activeflags);
+ }
+
+ if (worker->active) {
+ spin_unlock_irqrestore(&worker->active_lock, activeflags);
+ continue;
+ }
+
+ spin_lock_irqsave(&worker->urbs_lock, listflags);
+ if (list_empty(&worker->urbs)) {
+ spin_unlock_irqrestore(&worker->urbs_lock, listflags);
+ spin_unlock_irqrestore(&worker->active_lock, activeflags);
+ continue;
+ }
+
+ req = list_first_entry(&worker->urbs, struct urbreq, node);
+ list_del(&req->node);
+ spin_unlock_irqrestore(&worker->urbs_lock, listflags);
+
+ worker->active = req->urb;
+ spin_unlock_irqrestore(&worker->active_lock, activeflags);
+
+ status = usb_autopm_get_interface(worker->iface);
+ if (status < 0) {
+ DBG("unable to autoresume interface: %d\n", status);
+ if (status == -EPERM)
+ {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+ usbdev->auto_pm = 0;
+#endif
+ qc_suspend(worker->iface, PMSG_SUSPEND);
+ }
+
+ spin_lock_irqsave(&worker->urbs_lock, listflags);
+ list_add(&req->node, &worker->urbs);
+ spin_unlock_irqrestore(&worker->urbs_lock, listflags);
+
+ spin_lock_irqsave(&worker->active_lock, activeflags);
+ worker->active = NULL;
+ spin_unlock_irqrestore(&worker->active_lock, activeflags);
+
+ continue;
+ }
+
+ status = usb_submit_urb(worker->active, GFP_KERNEL);
+ if (status < 0) {
+ DBG("Failed to submit URB: %d. Packet dropped\n", status);
+ spin_lock_irqsave(&worker->active_lock, activeflags);
+ usb_free_urb(worker->active);
+ worker->active = NULL;
+ spin_unlock_irqrestore(&worker->active_lock, activeflags);
+ usb_autopm_put_interface(worker->iface);
+ complete(&worker->work);
+ }
+
+ kfree(req);
+ }
+
+ DBG("traffic thread exiting\n");
+ worker->thread = NULL;
+ return 0;
+}
+
+static int qcnet_startxmit(struct sk_buff *skb, struct net_device *netdev)
+{
+ unsigned long listflags;
+ struct qcusbnet *dev;
+ struct worker *worker;
+ struct urbreq *req;
+ void *data;
+ struct usbnet *usbnet = netdev_priv(netdev);
+
+ DBG("\n");
+
+ if (!usbnet || !usbnet->net) {
+ DBG("failed to get usbnet device\n");
+ return NETDEV_TX_BUSY;
+ }
+
+ dev = (struct qcusbnet *)usbnet->data[0];
+ if (!dev) {
+ DBG("failed to get QMIDevice\n");
+ return NETDEV_TX_BUSY;
+ }
+ worker = &dev->worker;
+
+ if (qc_isdown(dev, DOWN_DRIVER_SUSPENDED)) {
+ DBG("device is suspended\n");
+ dump_stack();
+ return NETDEV_TX_BUSY;
+ }
+
+ req = kmalloc(sizeof(*req), GFP_ATOMIC);
+ if (!req) {
+ DBG("unable to allocate URBList memory\n");
+ return NETDEV_TX_BUSY;
+ }
+
+ req->urb = usb_alloc_urb(0, GFP_ATOMIC);
+
+ if (!req->urb) {
+ kfree(req);
+ DBG("unable to allocate URB\n");
+ return NETDEV_TX_BUSY;
+ }
+
+ data = kmalloc(skb->len, GFP_ATOMIC);
+ if (!data) {
+ usb_free_urb(req->urb);
+ kfree(req);
+ DBG("unable to allocate URB data\n");
+ return NETDEV_TX_BUSY;
+ }
+ memcpy(data, skb->data, skb->len);
+
+ usb_fill_bulk_urb(req->urb, dev->usbnet->udev, dev->usbnet->out,
+ data, skb->len, qcnet_urbhook, worker);
+
+ spin_lock_irqsave(&worker->urbs_lock, listflags);
+ list_add_tail(&req->node, &worker->urbs);
+ spin_unlock_irqrestore(&worker->urbs_lock, listflags);
+
+ complete(&worker->work);
+
+ netdev->trans_start = jiffies;
+ dev_kfree_skb_any(skb);
+
+ return NETDEV_TX_OK;
+}
+
+static int qcnet_open(struct net_device *netdev)
+{
+ int status = 0;
+ struct qcusbnet *dev;
+ struct usbnet *usbnet = netdev_priv(netdev);
+
+ if (!usbnet) {
+ DBG("failed to get usbnet device\n");
+ return -ENXIO;
+ }
+
+ dev = (struct qcusbnet *)usbnet->data[0];
+ if (!dev) {
+ DBG("failed to get QMIDevice\n");
+ return -ENXIO;
+ }
+
+ DBG("\n");
+
+ dev->worker.iface = dev->iface;
+ INIT_LIST_HEAD(&dev->worker.urbs);
+ dev->worker.active = NULL;
+ spin_lock_init(&dev->worker.urbs_lock);
+ spin_lock_init(&dev->worker.active_lock);
+ init_completion(&dev->worker.work);
+
+ dev->worker.thread = kthread_run(qcnet_worker, &dev->worker, "qcnet_worker");
+ if (IS_ERR(dev->worker.thread))
+ {
+ DBG("AutoPM thread creation error\n");
+ return PTR_ERR(dev->worker.thread);
+ }
+
+ qc_cleardown(dev, DOWN_NET_IFACE_STOPPED);
+ if (dev->open)
+ {
+ status = dev->open(netdev);
+ if (status == 0)
+ {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+ usb_autopm_enable(dev->iface);
+#else
+ usb_autopm_put_interface(dev->iface);
+#endif
+ }
+ } else {
+ DBG("no USBNetOpen defined\n");
+ }
+
+ return status;
+}
+
+int qcnet_stop(struct net_device *netdev)
+{
+ struct qcusbnet *dev;
+ struct usbnet *usbnet = netdev_priv(netdev);
+
+ if (!usbnet || !usbnet->net) {
+ DBG("failed to get netdevice\n");
+ return -ENXIO;
+ }
+
+ dev = (struct qcusbnet *)usbnet->data[0];
+ if (!dev) {
+ DBG("failed to get QMIDevice\n");
+ return -ENXIO;
+ }
+
+ qc_setdown(dev, DOWN_NET_IFACE_STOPPED);
+ complete(&dev->worker.work);
+ kthread_stop(dev->worker.thread);
+ DBG("thread stopped\n");
+
+ if (dev->stop != NULL)
+ return dev->stop(netdev);
+ return 0;
+}
+
+static const struct driver_info qc_netinfo =
+{
+ .description = "QCUSBNet Ethernet Device",
+ .flags = FLAG_ETHER,
+ .bind = qcnet_bind,
+ .unbind = qcnet_unbind,
+ .data = 0,
+};
+
+#define MKVIDPID(v,p) \
+ { \
+ USB_DEVICE(v,p), \
+ .driver_info = (unsigned long)&qc_netinfo, \
+ }
+
+static const struct usb_device_id qc_vidpids [] =
+{
+ MKVIDPID(0x05c6, 0x9215), /* Acer Gobi 2000 */
+ MKVIDPID(0x05c6, 0x9265), /* Asus Gobi 2000 */
+ MKVIDPID(0x16d8, 0x8002), /* CMOTech Gobi 2000 */
+ MKVIDPID(0x413c, 0x8186), /* Dell Gobi 2000 */
+ MKVIDPID(0x1410, 0xa010), /* Entourage Gobi 2000 */
+ MKVIDPID(0x1410, 0xa011), /* Entourage Gobi 2000 */
+ MKVIDPID(0x1410, 0xa012), /* Entourage Gobi 2000 */
+ MKVIDPID(0x1410, 0xa013), /* Entourage Gobi 2000 */
+ MKVIDPID(0x03f0, 0x251d), /* HP Gobi 2000 */
+ MKVIDPID(0x05c6, 0x9205), /* Lenovo Gobi 2000 */
+ MKVIDPID(0x05c6, 0x920b), /* Generic Gobi 2000 */
+ MKVIDPID(0x04da, 0x250f), /* Panasonic Gobi 2000 */
+ MKVIDPID(0x05c6, 0x9245), /* Samsung Gobi 2000 */
+ MKVIDPID(0x1199, 0x9001), /* Sierra Wireless Gobi 2000 */
+ MKVIDPID(0x1199, 0x9002), /* Sierra Wireless Gobi 2000 */
+ MKVIDPID(0x1199, 0x9003), /* Sierra Wireless Gobi 2000 */
+ MKVIDPID(0x1199, 0x9004), /* Sierra Wireless Gobi 2000 */
+ MKVIDPID(0x1199, 0x9005), /* Sierra Wireless Gobi 2000 */
+ MKVIDPID(0x1199, 0x9006), /* Sierra Wireless Gobi 2000 */
+ MKVIDPID(0x1199, 0x9007), /* Sierra Wireless Gobi 2000 */
+ MKVIDPID(0x1199, 0x9008), /* Sierra Wireless Gobi 2000 */
+ MKVIDPID(0x1199, 0x9009), /* Sierra Wireless Gobi 2000 */
+ MKVIDPID(0x1199, 0x900a), /* Sierra Wireless Gobi 2000 */
+ MKVIDPID(0x05c6, 0x9225), /* Sony Gobi 2000 */
+ MKVIDPID(0x05c6, 0x9235), /* Top Global Gobi 2000 */
+ MKVIDPID(0x05c6, 0x9275), /* iRex Technologies Gobi 2000 */
+ { }
+};
+
+MODULE_DEVICE_TABLE(usb, qc_vidpids);
+
+int qcnet_probe(struct usb_interface *iface, const struct usb_device_id *vidpids)
+{
+ int status;
+ struct usbnet *usbnet;
+ struct qcusbnet *dev;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)
+ struct net_device_ops *netdevops;
+#endif
+
+ status = usbnet_probe(iface, vidpids);
+ if (status < 0) {
+ DBG("usbnet_probe failed %d\n", status);
+ return status;
+ }
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,23)
+ usbnet = usb_get_intfdata(iface);
+#else
+ usbnet = iface->dev.platform_data;
+#endif
+
+ if (!usbnet || !usbnet->net) {
+ DBG("failed to get netdevice\n");
+ return -ENXIO;
+ }
+
+ dev = kmalloc(sizeof(struct qcusbnet), GFP_KERNEL);
+ if (!dev) {
+ DBG("falied to allocate device buffers");
+ return -ENOMEM;
+ }
+
+ usbnet->data[0] = (unsigned long)dev;
+
+ dev->usbnet = usbnet;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)
+ dev->open = usbnet->net->open;
+ usbnet->net->open = qcnet_open;
+ dev->stop = usbnet->net->stop;
+ usbnet->net->stop = qcnet_stop;
+ usbnet->net->hard_start_xmit = qcnet_startxmit;
+ usbnet->net->tx_timeout = qcnet_txtimeout;
+#else
+ netdevops = kmalloc(sizeof(struct net_device_ops), GFP_KERNEL);
+ if (!netdevops) {
+ DBG("falied to allocate net device ops");
+ return -ENOMEM;
+ }
+ memcpy(netdevops, usbnet->net->netdev_ops, sizeof(struct net_device_ops));
+
+ dev->open = netdevops->ndo_open;
+ netdevops->ndo_open = qcnet_open;
+ dev->stop = netdevops->ndo_stop;
+ netdevops->ndo_stop = qcnet_stop;
+ netdevops->ndo_start_xmit = qcnet_startxmit;
+ netdevops->ndo_tx_timeout = qcnet_txtimeout;
+
+ usbnet->net->netdev_ops = netdevops;
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
+ memset(&(dev->usbnet->stats), 0, sizeof(struct net_device_stats));
+#else
+ memset(&(dev->usbnet->net->stats), 0, sizeof(struct net_device_stats));
+#endif
+
+ dev->iface = iface;
+ memset(&(dev->meid), '0', 14);
+
+ DBG("Mac Address:\n");
+ printhex(&dev->usbnet->net->dev_addr[0], 6);
+
+ dev->valid = false;
+ memset(&dev->qmi, 0, sizeof(struct qmidev));
+
+ dev->qmi.devclass = devclass;
+
+ INIT_LIST_HEAD(&dev->qmi.clients);
+ init_completion(&dev->worker.work);
+ spin_lock_init(&dev->qmi.clients_lock);
+
+ dev->down = 0;
+ qc_setdown(dev, DOWN_NO_NDIS_CONNECTION);
+ qc_setdown(dev, DOWN_NET_IFACE_STOPPED);
+
+ status = qc_register(dev);
+ if (status) {
+ qc_deregister(dev);
+ }
+
+ return status;
+}
+
+EXPORT_SYMBOL_GPL(qcnet_probe);
+
+static struct usb_driver qcusbnet =
+{
+ .name = "QCUSBNet2k",
+ .id_table = qc_vidpids,
+ .probe = qcnet_probe,
+ .disconnect = usbnet_disconnect,
+ .suspend = qc_suspend,
+ .resume = qc_resume,
+ .supports_autosuspend = true,
+};
+
+static int __init modinit(void)
+{
+ devclass = class_create(THIS_MODULE, "QCQMI");
+ if (IS_ERR(devclass)) {
+ DBG("error at class_create %ld\n", PTR_ERR(devclass));
+ return -ENOMEM;
+ }
+ printk(KERN_INFO "%s: %s\n", DRIVER_DESC, DRIVER_VERSION);
+ return usb_register(&qcusbnet);
+}
+module_init(modinit);
+
+static void __exit modexit(void)
+{
+ usb_deregister(&qcusbnet);
+ class_destroy(devclass);
+}
+module_exit(modexit);
+
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("Dual BSD/GPL");
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debuging enabled or not");
diff --git a/drivers/net/usb/qcusbnet/qcusbnet.h b/drivers/net/usb/qcusbnet/qcusbnet.h
new file mode 100644
index 0000000..2f20868
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/qcusbnet.h
@@ -0,0 +1,24 @@
+/* qcusbnet.h - gobi network device header
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef QCUSBNET_QCUSBNET_H
+#define QCUSBNET_QCUSBNET_H
+
+extern int qc_suspend(struct usb_interface *iface, pm_message_t event);
+
+#endif /* !QCUSBNET_QCUSBNET_H */
diff --git a/drivers/net/usb/qcusbnet/qmi.c b/drivers/net/usb/qcusbnet/qmi.c
new file mode 100755
index 0000000..2fc8ce8
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/qmi.c
@@ -0,0 +1,358 @@
+/* qmi.c - QMI protocol implementation
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "qmi.h"
+
+#include <linux/slab.h>
+
+struct qmux {
+ u8 tf; /* always 1 */
+ u16 len;
+ u8 ctrl;
+ u8 service;
+ u8 qmicid;
+} __attribute__((__packed__));
+
+struct getcid_req {
+ struct qmux header;
+ u8 req;
+ u8 tid;
+ u16 msgid;
+ u16 tlvsize;
+ u8 service;
+ u16 size;
+ u8 qmisvc;
+} __attribute__((__packed__));
+
+struct releasecid_req {
+ struct qmux header;
+ u8 req;
+ u8 tid;
+ u16 msgid;
+ u16 tlvsize;
+ u8 rlscid;
+ u16 size;
+ u16 cid;
+} __attribute__((__packed__));
+
+struct ready_req {
+ struct qmux header;
+ u8 req;
+ u8 tid;
+ u16 msgid;
+ u16 tlvsize;
+} __attribute__((__packed__));
+
+struct seteventreport_req {
+ struct qmux header;
+ u8 req;
+ u16 tid;
+ u16 msgid;
+ u16 tlvsize;
+ u8 reportchanrate;
+ u16 size;
+ u8 period;
+ u32 mask;
+} __attribute__((__packed__));
+
+struct getpkgsrvcstatus_req {
+ struct qmux header;
+ u8 req;
+ u16 tid;
+ u16 msgid;
+ u16 tlvsize;
+} __attribute__((__packed__));
+
+struct getmeid_req {
+ struct qmux header;
+ u8 req;
+ u16 tid;
+ u16 msgid;
+ u16 tlvsize;
+} __attribute__((__packed__));
+
+const size_t qmux_size = sizeof(struct qmux);
+
+void *qmictl_new_getcid(u8 tid, u8 svctype, size_t *size)
+{
+ struct getcid_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
+ if (!req)
+ return NULL;
+ req->req = 0x00;
+ req->tid = tid;
+ req->msgid = 0x0022;
+ req->tlvsize = 0x0004;
+ req->service = 0x01;
+ req->size = 0x0001;
+ req->qmisvc = svctype;
+ *size = sizeof(*req);
+ return req;
+}
+
+void *qmictl_new_releasecid(u8 tid, u16 cid, size_t *size)
+{
+ struct releasecid_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
+ if (!req)
+ return NULL;
+ req->req = 0x00;
+ req->tid = tid;
+ req->msgid = 0x0023;
+ req->tlvsize = 0x05;
+ req->rlscid = 0x01;
+ req->size = 0x0002;
+ req->cid = cid;
+ *size = sizeof(*req);
+ return req;
+}
+
+void *qmictl_new_ready(u8 tid, size_t *size)
+{
+ struct ready_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
+ if (!req)
+ return NULL;
+ req->req = 0x00;
+ req->tid = tid;
+ req->msgid = 0x21;
+ req->tlvsize = 0;
+ *size = sizeof(*req);
+ return req;
+}
+
+void *qmiwds_new_seteventreport(u8 tid, size_t *size)
+{
+ struct seteventreport_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
+ req->req = 0x00;
+ req->tid = tid;
+ req->msgid = 0x0001;
+ req->tlvsize = 0x0008;
+ req->reportchanrate = 0x11;
+ req->size = 0x0005;
+ req->period = 0x01;
+ req->mask = 0x000000ff;
+ *size = sizeof(*req);
+ return req;
+}
+
+void *qmiwds_new_getpkgsrvcstatus(u8 tid, size_t *size)
+{
+ struct getpkgsrvcstatus_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
+ if (!req)
+ return NULL;
+ req->req = 0x00;
+ req->tid = tid;
+ req->msgid = 0x22;
+ req->tlvsize = 0x0000;
+ *size = sizeof(*req);
+ return req;
+}
+
+void *qmidms_new_getmeid(u8 tid, size_t *size)
+{
+ struct getmeid_req *req = kmalloc(sizeof(*req), GFP_KERNEL);
+ if (!req)
+ return NULL;
+ req->req = 0x00;
+ req->tid = tid;
+ req->msgid = 0x25;
+ req->tlvsize = 0x0000;
+ *size = sizeof(*req);
+ return req;
+}
+
+int qmux_parse(u16 *cid, void *buf, size_t size)
+{
+ struct qmux *qmux = buf;
+
+ if (!buf || size < 12)
+ return -ENOMEM;
+
+ if (qmux->tf != 1 || qmux->len != size - 1 || qmux->ctrl != 0x80)
+ return -EINVAL;
+
+ *cid = (qmux->qmicid << 8) + qmux->service;
+ return sizeof(*qmux);
+}
+
+int qmux_fill(u16 cid, void *buf, size_t size)
+{
+ struct qmux *qmux = buf;
+
+ if (!buf || size < sizeof(*qmux))
+ return -ENOMEM;
+
+ qmux->tf = 1;
+ qmux->len = size - 1;
+ qmux->ctrl = 0;
+ qmux->service = cid & 0xff;
+ qmux->qmicid = cid >> 8;
+ return 0;
+}
+
+static u16 tlv_get(void *msg, u16 msgsize, u8 type, void *buf, u16 bufsize) {
+ u16 pos;
+ u16 msize = 0;
+
+ if (!msg || !buf)
+ return -ENOMEM;
+
+ for (pos = 4; pos + 3 < msgsize; pos += msize + 3) {
+ msize = *(u16 *)(msg + pos + 1);
+ if (*(u8 *)(msg + pos) == type) {
+ if (bufsize < msize)
+ return -ENOMEM;
+
+ memcpy(buf, msg + pos + 3, msize);
+ return msize;
+ }
+ }
+
+ return -ENOMSG;
+}
+
+int qmi_msgisvalid(void *msg, u16 size)
+{
+ char tlv[4];
+
+ if (tlv_get(msg, size, 2, &tlv[0], 4) == 4) {
+ if (*(u16 *)&tlv[0] != 0)
+ return *(u16 *)&tlv[2];
+ else
+ return 0;
+ }
+ return -ENOMSG;
+}
+
+int qmi_msgid(void *msg, u16 size)
+{
+ return size < 2 ? -ENODATA : *(u16 *)msg;
+}
+
+int qmictl_alloccid_resp(void *buf, u16 size, u16 *cid)
+{
+ int result;
+ u8 offset = sizeof(struct qmux) + 2;
+
+ if (!buf || size < offset)
+ return -ENOMEM;
+
+ buf = buf + offset;
+ size -= offset;
+
+ result = qmi_msgid(buf, size);
+ if (result != 0x22)
+ return -EFAULT;
+
+ result = qmi_msgisvalid(buf, size);
+ if (result != 0)
+ return -EFAULT;
+
+ result = tlv_get(buf, size, 0x01, cid, 2);
+ if (result != 2)
+ return -EFAULT;
+
+ return 0;
+}
+
+int qmictl_freecid_resp(void *buf, u16 size)
+{
+ int result;
+ u8 offset = sizeof(struct qmux) + 2;
+
+ if (!buf || size < offset)
+ return -ENOMEM;
+
+ buf = buf + offset;
+ size -= offset;
+
+ result = qmi_msgid(buf, size);
+ if (result != 0x23)
+ return -EFAULT;
+
+ result = qmi_msgisvalid(buf, size);
+ if (result != 0)
+ return -EFAULT;
+
+ return 0;
+}
+
+int qmiwds_event_resp(void *buf, u16 size, struct qmiwds_stats *stats)
+{
+ int result;
+ u8 status[2];
+
+ u8 offset = sizeof(struct qmux) + 3;
+
+ if (!buf || size < offset || !stats)
+ return -ENOMEM;
+
+ buf = buf + offset;
+ size -= offset;
+
+ result = qmi_msgid(buf, size);
+ if (result == 0x01) {
+ tlv_get(buf, size, 0x10, (void*)&stats->txok, 4);
+ tlv_get(buf, size, 0x11, (void*)&stats->rxok, 4);
+ tlv_get(buf, size, 0x12, (void*)&stats->txerr, 4);
+ tlv_get(buf, size, 0x13, (void*)&stats->rxerr, 4);
+ tlv_get(buf, size, 0x14, (void*)&stats->txofl, 4);
+ tlv_get(buf, size, 0x15, (void*)&stats->rxofl, 4);
+ tlv_get(buf, size, 0x19, (void*)&stats->txbytesok, 8);
+ tlv_get(buf, size, 0x1A, (void*)&stats->rxbytesok, 8);
+ } else if (result == 0x22) {
+ result = tlv_get(buf, size, 0x01, &status[0], 2);
+ if (result >= 1)
+ stats->linkstate = status[0] == 0x02;
+ if (result == 2)
+ stats->reconfigure = status[1] == 0x01;
+
+ if (result < 0)
+ return result;
+ } else {
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+int qmidms_meid_resp(void *buf, u16 size, char *meid, int meidsize)
+{
+ int result;
+
+ u8 offset = sizeof(struct qmux) + 3;
+
+ if (!buf || size < offset || meidsize < 14)
+ return -ENOMEM;
+
+ buf = buf + offset;
+ size -= offset;
+
+ result = qmi_msgid(buf, size);
+ if (result != 0x25)
+ return -EFAULT;
+
+ result = qmi_msgisvalid(buf, size);
+ if (result)
+ return -EFAULT;
+
+ result = tlv_get(buf, size, 0x12, (void*)meid, 14);
+ if (result != 14)
+ return -EFAULT;
+
+ return 0;
+}
+
diff --git a/drivers/net/usb/qcusbnet/qmi.h b/drivers/net/usb/qcusbnet/qmi.h
new file mode 100755
index 0000000..7954790
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/qmi.h
@@ -0,0 +1,67 @@
+/* qmi.h - QMI protocol header
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef QCUSBNET_QMI_H
+#define QCUSBNET_QMI_H
+
+#include <linux/types.h>
+
+#define QMICTL 0
+#define QMIWDS 1
+#define QMIDMS 2
+
+#define true 1
+#define false 0
+
+#define ENOMEM 12
+#define EFAULT 14
+#define EINVAL 22
+#define ENOMSG 42
+#define ENODATA 61
+
+int qmux_parse(u16 *cid, void *buf, size_t size);
+int qmux_fill(u16 cid, void *buf, size_t size);
+
+extern const size_t qmux_size;
+
+void *qmictl_new_getcid(u8 tid, u8 svctype, size_t *size);
+void *qmictl_new_releasecid(u8 tid, u16 cid, size_t *size);
+void *qmictl_new_ready(u8 tid, size_t *size);
+void *qmiwds_new_seteventreport(u8 tid, size_t *size);
+void *qmiwds_new_getpkgsrvcstatus(u8 tid, size_t *size);
+void *qmidms_new_getmeid(u8 tid, size_t *size);
+
+struct qmiwds_stats {
+ u32 txok;
+ u32 rxok;
+ u32 txerr;
+ u32 rxerr;
+ u32 txofl;
+ u32 rxofl;
+ u64 txbytesok;
+ u64 rxbytesok;
+ bool linkstate;
+ bool reconfigure;
+};
+
+int qmictl_alloccid_resp(void *buf, u16 size, u16 *cid);
+int qmictl_freecid_resp(void *buf, u16 size);
+int qmiwds_event_resp(void *buf, u16 size, struct qmiwds_stats *stats);
+int qmidms_meid_resp(void *buf, u16 size, char *meid, int meidsize);
+
+#endif /* !QCUSBNET_QMI_H */
diff --git a/drivers/net/usb/qcusbnet/qmidevice.c b/drivers/net/usb/qcusbnet/qmidevice.c
new file mode 100755
index 0000000..b5006be
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/qmidevice.c
@@ -0,0 +1,1621 @@
+/* qmidevice.c - gobi QMI device
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "qmidevice.h"
+#include "qcusbnet.h"
+
+struct readreq {
+ struct list_head node;
+ void *data;
+ u16 tid;
+ u16 size;
+};
+
+struct notifyreq {
+ struct list_head node;
+ void (* func)(struct qcusbnet *, u16, void *);
+ u16 tid;
+ void *data;
+};
+
+struct client {
+ struct list_head node;
+ u16 cid;
+ struct list_head reads;
+ struct list_head notifies;
+ struct list_head urbs;
+};
+
+struct urbsetup {
+ u8 type;
+ u8 code;
+ u16 value;
+ u16 index;
+ u16 len;
+};
+
+struct qmihandle {
+ u16 cid;
+ struct qcusbnet *dev;
+};
+
+extern int debug;
+static int qcusbnet2k_fwdelay = 0;
+
+static bool device_valid(struct qcusbnet *dev);
+static struct client *client_bycid(struct qcusbnet *dev, u16 cid);
+static bool client_addread(struct qcusbnet *dev, u16 cid, u16 tid, void *data, u16 size);
+static bool client_delread(struct qcusbnet *dev, u16 cid, u16 tid, void **data, u16 *size);
+static bool client_addnotify(struct qcusbnet *dev, u16 cid, u16 tid,
+ void (*hook)(struct qcusbnet *, u16 cid, void *),
+ void *data);
+static bool client_notify(struct qcusbnet *dev, u16 cid, u16 tid);
+static bool client_addurb(struct qcusbnet *dev, u16 cid, struct urb *urb);
+static struct urb *client_delurb(struct qcusbnet *dev, u16 cid);
+
+static int devqmi_open(struct inode *inode, struct file *file);
+static long devqmi_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+static int devqmi_close(struct file *file, fl_owner_t ftable);
+static ssize_t devqmi_read(struct file *file, char __user *buf, size_t size,
+ loff_t *pos);
+static ssize_t devqmi_write(struct file *file, const char __user *buf,
+ size_t size, loff_t *pos);
+
+static bool qmi_ready(struct qcusbnet *dev, u16 timeout);
+static void wds_callback(struct qcusbnet *dev, u16 cid, void *data);
+static int setup_wds_callback(struct qcusbnet *dev);
+static int qmidms_getmeid(struct qcusbnet *dev);
+
+#define IOCTL_QMI_GET_SERVICE_FILE 0x8BE0 + 1
+#define IOCTL_QMI_GET_DEVICE_VIDPID 0x8BE0 + 2
+#define IOCTL_QMI_GET_DEVICE_MEID 0x8BE0 + 3
+#define CDC_GET_ENCAPSULATED_RESPONSE 0x01A1ll
+#define CDC_CONNECTION_SPEED_CHANGE 0x08000000002AA1ll
+
+struct file_operations devqmi_fops =
+{
+ .owner = THIS_MODULE,
+ .read = devqmi_read,
+ .write = devqmi_write,
+ .unlocked_ioctl = devqmi_ioctl,
+ .open = devqmi_open,
+ .flush = devqmi_close,
+};
+
+#ifdef CONFIG_SMP
+static inline void assert_locked(struct qcusbnet *dev)
+{
+ BUG_ON(!spin_is_locked(&dev->qmi.clients_lock));
+}
+#else
+static inline void assert_locked(struct qcusbnet *dev)
+{
+
+}
+#endif
+
+static bool device_valid(struct qcusbnet *dev)
+{
+ return dev && dev->valid;
+}
+
+void printhex(const void *data, size_t size)
+{
+ const u8 *cdata = data;
+ char *buf;
+ size_t pos;
+
+ buf = kmalloc(size * 3 + 1, GFP_ATOMIC);
+ if (!buf) {
+ DBG("Unable to allocate buffer\n");
+ return;
+ }
+
+ memset(buf, 0 , size * 3 + 1);
+
+ for (pos = 0; pos < size; pos++) {
+ snprintf(buf + (pos * 3), 4, "%02X ", cdata[pos]);
+ }
+
+ DBG( " : %s\n", buf);
+
+ kfree(buf);
+}
+
+void qc_setdown(struct qcusbnet *dev, u8 reason)
+{
+ set_bit(reason, &dev->down);
+ netif_carrier_off(dev->usbnet->net);
+}
+
+void qc_cleardown(struct qcusbnet *dev, u8 reason)
+{
+ clear_bit(reason, &dev->down);
+ if (!dev->down)
+ netif_carrier_on(dev->usbnet->net);
+}
+
+bool qc_isdown(struct qcusbnet *dev, u8 reason)
+{
+ return test_bit(reason, &dev->down);
+}
+
+static void read_callback(struct urb *urb)
+{
+ struct list_head *node;
+ int result;
+ u16 cid;
+ struct client *client;
+ void *data;
+ void *copy;
+ u16 size;
+ struct qcusbnet *dev;
+ unsigned long flags;
+ u16 tid;
+
+ if (!urb) {
+ DBG("bad read URB\n");
+ return;
+ }
+
+ dev = urb->context;
+ if (!device_valid(dev)) {
+ DBG("Invalid device!\n");
+ return;
+ }
+
+ if (urb->status) {
+ DBG("Read status = %d\n", urb->status);
+ return;
+ }
+
+ DBG("Read %d bytes\n", urb->actual_length);
+
+ data = urb->transfer_buffer;
+ size = urb->actual_length;
+
+ printhex(data, size);
+
+ result = qmux_parse(&cid, data, size);
+ if (result < 0) {
+ DBG("Read error parsing QMUX %d\n", result);
+ return;
+ }
+
+ if (size < result + 3) {
+ DBG("Data buffer too small to parse\n");
+ return;
+ }
+
+ if (cid == QMICTL)
+ tid = *(u8*)(data + result + 1);
+ else
+ tid = *(u16*)(data + result + 1);
+ spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+
+ list_for_each(node, &dev->qmi.clients) {
+ client = list_entry(node, struct client, node);
+ if (client->cid == cid || (client->cid | 0xff00) == cid) {
+ copy = kmalloc(size, GFP_ATOMIC);
+ memcpy(copy, data, size);
+ if (!client_addread(dev, client->cid, tid, copy, size)) {
+ DBG("Error allocating pReadMemListEntry "
+ "read will be discarded\n");
+ kfree(copy);
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ return;
+ }
+
+ DBG("Creating new readListEntry for client 0x%04X, TID %x\n",
+ cid, tid);
+
+ client_notify(dev, client->cid, tid);
+
+ if (cid >> 8 != 0xff)
+ break;
+ }
+ }
+
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+}
+
+static void int_callback(struct urb *urb)
+{
+ int status;
+ int interval;
+ struct qcusbnet *dev = (struct qcusbnet *)urb->context;
+
+ if (!device_valid(dev)) {
+ DBG("Invalid device!\n");
+ return;
+ }
+
+ if (urb->status) {
+ DBG("Int status = %d\n", urb->status);
+ if (urb->status != -EOVERFLOW)
+ return;
+ } else {
+ if ((urb->actual_length == 8)
+ && (*(u64*)urb->transfer_buffer == CDC_GET_ENCAPSULATED_RESPONSE)) {
+ usb_fill_control_urb(dev->qmi.readurb, dev->usbnet->udev,
+ usb_rcvctrlpipe(dev->usbnet->udev, 0),
+ (unsigned char *)dev->qmi.readsetup,
+ dev->qmi.readbuf,
+ DEFAULT_READ_URB_LENGTH,
+ read_callback, dev);
+ status = usb_submit_urb(dev->qmi.readurb, GFP_ATOMIC);
+ if (status) {
+ DBG("Error submitting Read URB %d\n", status);
+ return;
+ }
+ } else if ((urb->actual_length == 16)
+ && (*(u64*)urb->transfer_buffer == CDC_CONNECTION_SPEED_CHANGE)) {
+ /* if upstream or downstream is 0, stop traffic.
+ * Otherwise resume it */
+ if ((*(u32*)(urb->transfer_buffer + 8) == 0)
+ || (*(u32*)(urb->transfer_buffer + 12) == 0)) {
+ qc_setdown(dev, DOWN_CDC_CONNECTION_SPEED);
+ DBG("traffic stopping due to CONNECTION_SPEED_CHANGE\n");
+ } else {
+ qc_cleardown(dev, DOWN_CDC_CONNECTION_SPEED);
+ DBG("resuming traffic due to CONNECTION_SPEED_CHANGE\n");
+ }
+ } else {
+ DBG("ignoring invalid interrupt in packet\n");
+ printhex(urb->transfer_buffer, urb->actual_length);
+ }
+ }
+
+ interval = (dev->usbnet->udev->speed == USB_SPEED_HIGH) ? 7 : 3;
+
+ usb_fill_int_urb(urb, urb->dev, urb->pipe, urb->transfer_buffer,
+ urb->transfer_buffer_length, urb->complete,
+ urb->context, interval);
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (status)
+ DBG("Error re-submitting Int URB %d\n", status);
+ return;
+}
+
+int qc_startread(struct qcusbnet *dev)
+{
+ int interval;
+
+ if (!device_valid(dev)) {
+ DBG("Invalid device!\n");
+ return -ENXIO;
+ }
+
+ dev->qmi.readurb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->qmi.readurb) {
+ DBG("Error allocating read urb\n");
+ return -ENOMEM;
+ }
+
+ dev->qmi.inturb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->qmi.inturb) {
+ usb_free_urb(dev->qmi.readurb);
+ DBG("Error allocating int urb\n");
+ return -ENOMEM;
+ }
+
+ dev->qmi.readbuf = kmalloc(DEFAULT_READ_URB_LENGTH, GFP_KERNEL);
+ if (!dev->qmi.readbuf) {
+ usb_free_urb(dev->qmi.readurb);
+ usb_free_urb(dev->qmi.inturb);
+ DBG("Error allocating read buffer\n");
+ return -ENOMEM;
+ }
+
+ dev->qmi.intbuf = kmalloc(DEFAULT_READ_URB_LENGTH, GFP_KERNEL);
+ if (!dev->qmi.intbuf) {
+ usb_free_urb(dev->qmi.readurb);
+ usb_free_urb(dev->qmi.inturb);
+ kfree(dev->qmi.readbuf);
+ DBG("Error allocating int buffer\n");
+ return -ENOMEM;
+ }
+
+ dev->qmi.readsetup = kmalloc(sizeof(*dev->qmi.readsetup), GFP_KERNEL);
+ if (!dev->qmi.readsetup) {
+ usb_free_urb(dev->qmi.readurb);
+ usb_free_urb(dev->qmi.inturb);
+ kfree(dev->qmi.readbuf);
+ kfree(dev->qmi.intbuf);
+ DBG("Error allocating setup packet buffer\n");
+ return -ENOMEM;
+ }
+
+ dev->qmi.readsetup->type = 0xA1;
+ dev->qmi.readsetup->code = 1;
+ dev->qmi.readsetup->value = 0;
+ dev->qmi.readsetup->index = 0;
+ dev->qmi.readsetup->len = DEFAULT_READ_URB_LENGTH;
+
+ interval = (dev->usbnet->udev->speed == USB_SPEED_HIGH) ? 7 : 3;
+
+ usb_fill_int_urb(dev->qmi.inturb, dev->usbnet->udev,
+ usb_rcvintpipe(dev->usbnet->udev, 0x81),
+ dev->qmi.intbuf, DEFAULT_READ_URB_LENGTH,
+ int_callback, dev, interval);
+ return usb_submit_urb(dev->qmi.inturb, GFP_KERNEL);
+}
+
+void qc_stopread(struct qcusbnet * dev)
+{
+ if (dev->qmi.readurb) {
+ DBG("Killng read URB\n");
+ usb_kill_urb(dev->qmi.readurb);
+ }
+
+ if (dev->qmi.inturb) {
+ DBG("Killng int URB\n");
+ usb_kill_urb(dev->qmi.inturb);
+ }
+
+ kfree(dev->qmi.readsetup);
+ dev->qmi.readsetup = NULL;
+ kfree(dev->qmi.readbuf);
+ dev->qmi.readbuf = NULL;
+ kfree(dev->qmi.intbuf);
+ dev->qmi.intbuf = NULL;
+
+ usb_free_urb(dev->qmi.readurb);
+ dev->qmi.readurb = NULL;
+ usb_free_urb(dev->qmi.inturb);
+ dev->qmi.inturb = NULL;
+}
+
+static int read_async(struct qcusbnet *dev, u16 cid, u16 tid,
+ void (*hook)(struct qcusbnet*, u16, void *), void *data)
+{
+ struct list_head *node;
+ struct client *client;
+ struct readreq *readreq;
+
+ unsigned long flags;
+
+ if (!device_valid(dev)) {
+ DBG("Invalid device!\n");
+ return -ENXIO;
+ }
+
+ spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+
+ client = client_bycid(dev, cid);
+ if (!client) {
+ DBG("Could not find matching client ID 0x%04X\n", cid);
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ return -ENXIO;
+ }
+
+ list_for_each(node, &client->reads) {
+ readreq = list_entry(node, struct readreq, node);
+ if (!tid || tid == readreq->tid) {
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ hook(dev, cid, data);
+ return 0;
+ }
+ }
+
+ if (!client_addnotify(dev, cid, tid, hook, data))
+ DBG("Unable to register for notification\n");
+
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ return 0;
+}
+
+static void upsem(struct qcusbnet *dev, u16 cid, void *data)
+{
+ DBG("0x%04X\n", cid);
+ up((struct semaphore *)data);
+}
+
+static int read_sync(struct qcusbnet *dev, void **buf, u16 cid, u16 tid)
+{
+ struct list_head *node;
+ int result;
+ struct client * client;
+ struct notifyreq *notify;
+ struct semaphore sem;
+ void * data;
+ unsigned long flags;
+ u16 size;
+
+ if (!device_valid(dev)) {
+ DBG("Invalid device!\n");
+ return -ENXIO;
+ }
+
+ spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+
+ client = client_bycid(dev, cid);
+ if (!client) {
+ DBG("Could not find matching client ID 0x%04X\n", cid);
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ return -ENXIO;
+ }
+
+ while (!client_delread(dev, cid, tid, &data, &size)) {
+ sema_init(&sem, 0);
+ if (!client_addnotify(dev, cid, tid, upsem, &sem)) {
+ DBG("unable to register for notification\n");
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ return -EFAULT;
+ }
+
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+
+ result = down_interruptible(&sem);
+ if (result) {
+ DBG("Interrupted %d\n", result);
+ spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+ list_for_each(node, &client->notifies) {
+ notify = list_entry(node, struct notifyreq, node);
+ if (notify->data == &sem) {
+ list_del(¬ify->node);
+ kfree(notify);
+ break;
+ }
+ }
+
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ return -EINTR;
+ }
+
+ if (!device_valid(dev)) {
+ DBG("Invalid device!\n");
+ return -ENXIO;
+ }
+
+ spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+ }
+
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ *buf = data;
+ return size;
+}
+
+static void write_callback(struct urb *urb)
+{
+ if (!urb) {
+ DBG("null urb\n");
+ return;
+ }
+
+ DBG("Write status/size %d/%d\n", urb->status, urb->actual_length);
+ up((struct semaphore *)urb->context);
+}
+
+static int write_sync(struct qcusbnet *dev, char *buf, int size, u16 cid)
+{
+ int result;
+ struct semaphore sem;
+ struct urb *urb;
+ struct urbsetup setup;
+ unsigned long flags;
+
+ if (!device_valid(dev)) {
+ DBG("Invalid device!\n");
+ return -ENXIO;
+ }
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ DBG("URB mem error\n");
+ return -ENOMEM;
+ }
+
+ result = qmux_fill(cid, buf, size);
+ if (result < 0) {
+ usb_free_urb(urb);
+ return result;
+ }
+
+ /* CDC Send Encapsulated Request packet */
+ setup.type = 0x21;
+ setup.code = 0;
+ setup.value = 0;
+ setup.index = 0;
+ setup.len = 0;
+ setup.len = size;
+
+ usb_fill_control_urb(urb, dev->usbnet->udev,
+ usb_sndctrlpipe(dev->usbnet->udev, 0),
+ (unsigned char *)&setup, (void*)buf, size,
+ NULL, dev);
+
+ DBG("Actual Write:\n");
+ printhex(buf, size);
+
+ sema_init(&sem, 0);
+
+ urb->complete = write_callback;
+ urb->context = &sem;
+
+ result = usb_autopm_get_interface(dev->iface);
+ if (result < 0) {
+ DBG("unable to resume interface: %d\n", result);
+ if (result == -EPERM) {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+ dev->usbnet->udev->auto_pm = 0;
+#endif
+ qc_suspend(dev->iface, PMSG_SUSPEND);
+ }
+ return result;
+ }
+
+ spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+
+ if (!client_addurb(dev, cid, urb)) {
+ usb_free_urb(urb);
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ usb_autopm_put_interface(dev->iface);
+ return -EINVAL;
+ }
+
+ result = usb_submit_urb(urb, GFP_KERNEL);
+ if (result < 0) {
+ DBG("submit URB error %d\n", result);
+ if (client_delurb(dev, cid) != urb) {
+ DBG("Didn't get write URB back\n");
+ }
+
+ usb_free_urb(urb);
+
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ usb_autopm_put_interface(dev->iface);
+ return result;
+ }
+
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ result = down_interruptible(&sem);
+ if (!device_valid(dev)) {
+ DBG("Invalid device!\n");
+ return -ENXIO;
+ }
+
+ usb_autopm_put_interface(dev->iface);
+ spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+ if (client_delurb(dev, cid) != urb)
+ {
+ DBG("Didn't get write URB back\n");
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ return -EINVAL;
+ }
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+
+ if (!result) {
+ if (!urb->status) {
+ result = size;
+ } else {
+ DBG("bad status = %d\n", urb->status);
+ result = urb->status;
+ }
+ } else {
+ DBG("Interrupted %d !!!\n", result);
+ DBG("Device may be in bad state and need reset !!!\n");
+ usb_kill_urb(urb);
+ }
+
+ usb_free_urb(urb);
+ return result;
+}
+
+static int client_alloc(struct qcusbnet *dev, u8 type)
+{
+ u16 cid;
+ struct client *client;
+ int result;
+ void * wbuf;
+ size_t wbufsize;
+ void * rbuf;
+ u16 rbufsize;
+ unsigned long flags;
+ u8 tid;
+
+ if (!device_valid(dev)) {
+ DBG("Invalid device!\n");
+ return -ENXIO;
+ }
+
+ if (type) {
+ tid = atomic_add_return(1, &dev->qmi.qmitid);
+ if (!tid)
+ atomic_add_return(1, &dev->qmi.qmitid);
+ wbuf = qmictl_new_getcid(tid, type, &wbufsize);
+ if (!wbuf)
+ return -ENOMEM;
+ result = write_sync(dev, wbuf, wbufsize, QMICTL);
+ kfree(wbuf);
+
+ if (result < 0)
+ return result;
+
+ result = read_sync(dev, &rbuf, QMICTL, tid);
+ if (result < 0) {
+ DBG("bad read data %d\n", result);
+ return result;
+ }
+ rbufsize = result;
+
+ result = qmictl_alloccid_resp(rbuf, rbufsize, &cid);
+ kfree(rbuf);
+
+ if (result < 0)
+ return result;
+ } else {
+ cid = 0;
+ }
+
+ spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+ if (client_bycid(dev, cid)) {
+ DBG("Client memory already exists\n");
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ return -ETOOMANYREFS;
+ }
+
+ client = kmalloc(sizeof(*client), GFP_ATOMIC);
+ if (!client) {
+ DBG("Error allocating read list\n");
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ return -ENOMEM;
+ }
+
+ list_add_tail(&client->node, &dev->qmi.clients);
+ client->cid = cid;
+ INIT_LIST_HEAD(&client->reads);
+ INIT_LIST_HEAD(&client->notifies);
+ INIT_LIST_HEAD(&client->urbs);
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ return cid;
+}
+
+static void client_free(struct qcusbnet *dev, u16 cid)
+{
+ struct list_head *node, *tmp;
+ int result;
+ struct client *client;
+ struct urb * urb;
+ void * data;
+ u16 size;
+ void * wbuf;
+ size_t wbufsize;
+ void * rbuf;
+ u16 rbufsize;
+ unsigned long flags;
+ u8 tid;
+
+ if (!device_valid(dev)) {
+ DBG("invalid device\n");
+ return;
+ }
+
+ DBG("releasing 0x%04X\n", cid);
+
+ if (cid != QMICTL)
+ {
+ tid = atomic_add_return(1, &dev->qmi.qmitid);
+ if (!tid)
+ tid = atomic_add_return(1, &dev->qmi.qmitid);
+ wbuf = qmictl_new_releasecid(tid, cid, &wbufsize);
+ if (!wbuf) {
+ DBG("memory error\n");
+ } else {
+ result = write_sync(dev, wbuf, wbufsize, QMICTL);
+ kfree(wbuf);
+
+ if (result < 0) {
+ DBG("bad write status %d\n", result);
+ } else {
+ result = read_sync(dev, &rbuf, QMICTL, tid);
+ if (result < 0) {
+ DBG("bad read status %d\n", result);
+ } else {
+ rbufsize = result;
+ result = qmictl_freecid_resp(rbuf, rbufsize);
+ kfree(rbuf);
+ if (result < 0)
+ DBG("error %d parsing response\n", result);
+ }
+ }
+ }
+ }
+
+ spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+ list_for_each_safe(node, tmp, &dev->qmi.clients) {
+ client = list_entry(node, struct client, node);
+ if (client->cid == cid) {
+ while (client_notify(dev, cid, 0));
+
+ urb = client_delurb(dev, cid);
+ while (urb != NULL) {
+ usb_kill_urb(urb);
+ usb_free_urb(urb);
+ urb = client_delurb(dev, cid);
+ }
+
+ while (client_delread(dev, cid, 0, &data, &size))
+ kfree(data);
+
+ list_del(&client->node);
+ kfree(client);
+ }
+ }
+
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+}
+
+struct client *client_bycid(struct qcusbnet *dev, u16 cid)
+{
+ struct list_head *node;
+ struct client *client;
+
+ if (!device_valid(dev)) {
+ DBG("Invalid device\n");
+ return NULL;
+ }
+
+ assert_locked(dev);
+
+ list_for_each(node, &dev->qmi.clients) {
+ client = list_entry(node, struct client, node);
+ if (client->cid == cid)
+ return client;
+ }
+
+ DBG("Could not find client mem 0x%04X\n", cid);
+ return NULL;
+}
+
+static bool client_addread(struct qcusbnet *dev, u16 cid, u16 tid, void *data,
+ u16 size)
+{
+ struct client *client;
+ struct readreq *req;
+
+ assert_locked(dev);
+
+ client = client_bycid(dev, cid);
+ if (!client) {
+ DBG("Could not find this client's memory 0x%04X\n", cid);
+ return false;
+ }
+
+ req = kmalloc(sizeof(*req), GFP_ATOMIC);
+ if (!req) {
+ DBG("Mem error\n");
+ return false;
+ }
+
+ req->data = data;
+ req->size = size;
+ req->tid = tid;
+
+ list_add_tail(&req->node, &client->reads);
+
+ return true;
+}
+
+static bool client_delread(struct qcusbnet *dev, u16 cid, u16 tid, void **data,
+ u16 *size)
+{
+ struct client *client;
+ struct readreq *req;
+ struct list_head *node;
+
+ assert_locked(dev);
+
+ client = client_bycid(dev, cid);
+ if (!client) {
+ DBG("Could not find this client's memory 0x%04X\n", cid);
+ return false;
+ }
+
+ list_for_each(node, &client->reads) {
+ req = list_entry(node, struct readreq, node);
+ if (!tid || tid == req->tid) {
+ *data = req->data;
+ *size = req->size;
+ list_del(&req->node);
+ kfree(req);
+ return true;
+ }
+
+ DBG("skipping 0x%04X data TID = %x\n", cid, req->tid);
+ }
+
+ DBG("No read memory to pop, Client 0x%04X, TID = %x\n", cid, tid);
+ return false;
+}
+
+static bool client_addnotify(struct qcusbnet *dev, u16 cid, u16 tid,
+ void (*hook)(struct qcusbnet *, u16, void *),
+ void *data)
+{
+ struct client *client;
+ struct notifyreq *req;
+
+ assert_locked(dev);
+
+ client = client_bycid(dev, cid);
+ if (!client) {
+ DBG("Could not find this client's memory 0x%04X\n", cid);
+ return false;
+ }
+
+ req = kmalloc(sizeof(*req), GFP_ATOMIC);
+ if (!req) {
+ DBG("Mem error\n");
+ return false;
+ }
+
+ list_add_tail(&req->node, &client->notifies);
+ req->func = hook;
+ req->data = data;
+ req->tid = tid;
+
+ return true;
+}
+
+static bool client_notify(struct qcusbnet *dev, u16 cid, u16 tid)
+{
+ struct client *client;
+ struct notifyreq *delnotify, *notify;
+ struct list_head *node;
+
+ assert_locked(dev);
+
+ client = client_bycid(dev, cid);
+ if (!client) {
+ DBG("Could not find this client's memory 0x%04X\n", cid);
+ return false;
+ }
+
+ delnotify = NULL;
+
+ list_for_each(node, &client->notifies) {
+ notify = list_entry(node, struct notifyreq, node);
+ if (!tid || !notify->tid || tid == notify->tid) {
+ delnotify = notify;
+ break;
+ }
+
+ DBG("skipping data TID = %x\n", notify->tid);
+ }
+
+ if (delnotify) {
+ list_del(&delnotify->node);
+ if (delnotify->func) {
+ spin_unlock(&dev->qmi.clients_lock);
+ delnotify->func(dev, cid, delnotify->data);
+ spin_lock(&dev->qmi.clients_lock);
+ }
+ kfree(delnotify);
+ return true;
+ }
+
+ DBG("no one to notify for TID %x\n", tid);
+ return false;
+}
+
+static bool client_addurb(struct qcusbnet *dev, u16 cid, struct urb *urb)
+{
+ struct client *client;
+ struct urbreq *req;
+
+ assert_locked(dev);
+
+ client = client_bycid(dev, cid);
+ if (!client) {
+ DBG("Could not find this client's memory 0x%04X\n", cid);
+ return false;
+ }
+
+ req = kmalloc(sizeof(*req), GFP_ATOMIC);
+ if (!req) {
+ DBG("Mem error\n");
+ return false;
+ }
+
+ req->urb = urb;
+ list_add_tail(&req->node, &client->urbs);
+
+ return true;
+}
+
+static struct urb *client_delurb(struct qcusbnet *dev, u16 cid)
+{
+ struct client *client;
+ struct urbreq *req;
+ struct urb *urb;
+
+ assert_locked(dev);
+
+ client = client_bycid(dev, cid);
+ if (!client) {
+ DBG("Could not find this client's memory 0x%04X\n", cid);
+ return NULL;
+ }
+
+ if (list_empty(&client->urbs)) {
+ DBG("No URB's to pop\n");
+ return NULL;
+ }
+
+ req = list_first_entry(&client->urbs, struct urbreq, node);
+ list_del(&req->node);
+ urb = req->urb;
+ kfree(req);
+ return urb;
+}
+
+static int devqmi_open(struct inode *inode, struct file *file)
+{
+ struct qmihandle * handle;
+ struct qmidev *qmidev = container_of(inode->i_cdev, struct qmidev, cdev);
+ struct qcusbnet * dev = container_of(qmidev, struct qcusbnet, qmi);
+
+ if (!device_valid(dev)) {
+ DBG("Invalid device\n");
+ return -ENXIO;
+ }
+
+ file->private_data = kmalloc(sizeof(struct qmihandle), GFP_KERNEL);
+ if (!file->private_data) {
+ DBG("Mem error\n");
+ return -ENOMEM;
+ }
+
+ handle = (struct qmihandle *)file->private_data;
+ handle->cid = (u16)-1;
+ handle->dev = dev;
+
+ return 0;
+}
+
+static long devqmi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int result;
+ u32 vidpid;
+
+ struct qmihandle *handle = (struct qmihandle *)file->private_data;
+
+ if (!handle) {
+ DBG("Bad file data\n");
+ return -EBADF;
+ }
+
+ if (!device_valid(handle->dev)) {
+ DBG("Invalid device! Updating f_ops\n");
+ file->f_op = file->f_dentry->d_inode->i_fop;
+ return -ENXIO;
+ }
+
+ switch (cmd) {
+ case IOCTL_QMI_GET_SERVICE_FILE:
+
+ DBG("Setting up QMI for service %lu\n", arg);
+ if (!(u8)arg) {
+ DBG("Cannot use QMICTL from userspace\n");
+ return -EINVAL;
+ }
+
+ if (handle->cid != (u16)-1) {
+ DBG("Close the current connection before opening a new one\n");
+ return -EBADR;
+ }
+
+ result = client_alloc(handle->dev, (u8)arg);
+ if (result < 0)
+ return result;
+ handle->cid = result;
+
+ return 0;
+ break;
+
+
+ case IOCTL_QMI_GET_DEVICE_VIDPID:
+ if (!arg) {
+ DBG("Bad VIDPID buffer\n");
+ return -EINVAL;
+ }
+
+ if (!handle->dev->usbnet) {
+ DBG("Bad usbnet\n");
+ return -ENOMEM;
+ }
+
+ if (!handle->dev->usbnet->udev) {
+ DBG("Bad udev\n");
+ return -ENOMEM;
+ }
+
+ vidpid = ((le16_to_cpu(handle->dev->usbnet->udev->descriptor.idVendor) << 16)
+ + le16_to_cpu(handle->dev->usbnet->udev->descriptor.idProduct));
+
+ result = copy_to_user((unsigned int *)arg, &vidpid, 4);
+ if (result)
+ DBG("Copy to userspace failure\n");
+
+ return result;
+ break;
+
+ case IOCTL_QMI_GET_DEVICE_MEID:
+ if (!arg) {
+ DBG("Bad MEID buffer\n");
+ return -EINVAL;
+ }
+
+ result = copy_to_user((unsigned int *)arg, &handle->dev->meid[0], 14);
+ if (result)
+ DBG("copy to userspace failure\n");
+
+ return result;
+ break;
+ default:
+ return -EBADRQC;
+ }
+}
+
+static int devqmi_close(struct file *file, fl_owner_t ftable)
+{
+ struct qmihandle * handle = (struct qmihandle *)file->private_data;
+ struct list_head * tasks;
+ struct task_struct * task;
+ struct fdtable * fdtable;
+ int count = 0;
+ int used = 0;
+ unsigned long flags;
+
+ if (!handle) {
+ DBG("bad file data\n");
+ return -EBADF;
+ }
+
+ if (file_count(file) != 1) {
+ /* XXX: This can't possibly be safe. We don't hold any sort of
+ * lock here, and we're walking a list of threads... */
+ list_for_each(tasks, ¤t->group_leader->tasks) {
+ task = container_of(tasks, struct task_struct, tasks);
+ if (!task || !task->files)
+ continue;
+ spin_lock_irqsave(&task->files->file_lock, flags);
+ fdtable = files_fdtable(task->files);
+ for (count = 0; count < fdtable->max_fds; count++) {
+ /* Before this function was called, this file was removed
+ * from our task's file table so if we find it in a file
+ * table then it is being used by another task
+ */
+ if (fdtable->fd[count] == file) {
+ used++;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&task->files->file_lock, flags);
+ }
+
+ if (used > 0) {
+ DBG("not closing, as this FD is open by %d other process\n", used);
+ return 0;
+ }
+ }
+
+ if (!device_valid(handle->dev)) {
+ DBG("Invalid device! Updating f_ops\n");
+ file->f_op = file->f_dentry->d_inode->i_fop;
+ return -ENXIO;
+ }
+
+ DBG("0x%04X\n", handle->cid);
+
+ file->private_data = NULL;
+
+ if (handle->cid != (u16)-1)
+ client_free(handle->dev, handle->cid);
+
+ kfree(handle);
+ return 0;
+}
+
+static ssize_t devqmi_read(struct file *file, char __user *buf, size_t size,
+ loff_t *pos)
+{
+ int result;
+ void * data = NULL;
+ void * smalldata;
+ struct qmihandle * handle = (struct qmihandle *)file->private_data;
+
+ if (!handle) {
+ DBG("Bad file data\n");
+ return -EBADF;
+ }
+
+ if (!device_valid(handle->dev)) {
+ DBG("Invalid device! Updating f_ops\n");
+ file->f_op = file->f_dentry->d_inode->i_fop;
+ return -ENXIO;
+ }
+
+ if (handle->cid == (u16)-1) {
+ DBG("Client ID must be set before reading 0x%04X\n",
+ handle->cid);
+ return -EBADR;
+ }
+
+ result = read_sync(handle->dev, &data, handle->cid, 0);
+ if (result <= 0)
+ return result;
+
+ result -= qmux_size;
+ smalldata = data + qmux_size;
+
+ if (result > size) {
+ DBG("Read data is too large for amount user has requested\n");
+ kfree(data);
+ return -EOVERFLOW;
+ }
+
+ if (copy_to_user(buf, smalldata, result)) {
+ DBG("Error copying read data to user\n");
+ result = -EFAULT;
+ }
+
+ kfree(data);
+ return result;
+}
+
+static ssize_t devqmi_write (struct file *file, const char __user * buf,
+ size_t size, loff_t *pos)
+{
+ int status;
+ void *wbuf;
+ struct qmihandle *handle = (struct qmihandle *)file->private_data;
+
+ if (!handle) {
+ DBG("Bad file data\n");
+ return -EBADF;
+ }
+
+ if (!device_valid(handle->dev)) {
+ DBG("Invalid device! Updating f_ops\n");
+ file->f_op = file->f_dentry->d_inode->i_fop;
+ return -ENXIO;
+ }
+
+ if (handle->cid == (u16)-1) {
+ DBG("Client ID must be set before writing 0x%04X\n",
+ handle->cid);
+ return -EBADR;
+ }
+
+ wbuf = kmalloc(size + qmux_size, GFP_KERNEL);
+ if (!wbuf)
+ return -ENOMEM;
+ status = copy_from_user(wbuf + qmux_size, buf, size);
+ if (status) {
+ DBG("Unable to copy data from userspace %d\n", status);
+ kfree(wbuf);
+ return status;
+ }
+
+ status = write_sync(handle->dev, wbuf, size + qmux_size,
+ handle->cid);
+
+ kfree(wbuf);
+ if (status == size + qmux_size)
+ return size;
+ return status;
+}
+
+int qc_register(struct qcusbnet *dev)
+{
+ int result;
+ int qmiidx = 0;
+ dev_t devno;
+ char * name;
+
+ dev->valid = true;
+ result = client_alloc(dev, QMICTL);
+ if (result) {
+ dev->valid = false;
+ return result;
+ }
+ atomic_set(&dev->qmi.qmitid, 1);
+
+ result = qc_startread(dev);
+ if (result) {
+ dev->valid = false;
+ return result;
+ }
+
+ if (!qmi_ready(dev, 30000)) {
+ DBG("Device unresponsive to QMI\n");
+ return -ETIMEDOUT;
+ }
+
+ result = setup_wds_callback(dev);
+ if (result) {
+ dev->valid = false;
+ return result;
+ }
+
+ result = qmidms_getmeid(dev);
+ if (result) {
+ dev->valid = false;
+ return result;
+ }
+
+ result = alloc_chrdev_region(&devno, 0, 1, "qcqmi");
+ if (result < 0)
+ return result;
+
+ cdev_init(&dev->qmi.cdev, &devqmi_fops);
+ dev->qmi.cdev.owner = THIS_MODULE;
+ dev->qmi.cdev.ops = &devqmi_fops;
+
+ result = cdev_add(&dev->qmi.cdev, devno, 1);
+ if (result) {
+ DBG("error adding cdev\n");
+ return result;
+ }
+
+ name = strstr(dev->usbnet->net->name, "usb");
+ if (!name) {
+ DBG("Bad net name: %s\n", dev->usbnet->net->name);
+ return -ENXIO;
+ }
+ name += strlen("usb");
+ qmiidx = simple_strtoul(name, NULL, 10);
+ if (qmiidx < 0) {
+ DBG("Bad minor number\n");
+ return -ENXIO;
+ }
+
+ printk(KERN_INFO "creating qcqmi%d\n", qmiidx);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
+ device_create(dev->qmi.devclass, NULL, devno, NULL, "qcqmi%d", qmiidx);
+#else
+ device_create(dev->qmi.devclass, NULL, devno, "qcqmi%d", qmiidx);
+#endif
+
+ dev->qmi.devnum = devno;
+ return 0;
+}
+
+void qc_deregister(struct qcusbnet *dev)
+{
+ struct list_head *node;
+ struct client *client;
+ struct inode * inode;
+ struct list_head * inodes;
+ struct list_head * tasks;
+ struct task_struct * task;
+ struct fdtable * fdtable;
+ struct file * file;
+ unsigned long flags;
+ int count = 0;
+
+ if (!device_valid(dev)) {
+ DBG("wrong device\n");
+ return;
+ }
+
+ list_for_each(node, &dev->qmi.clients) {
+ client = list_entry(node, struct client, node);
+ DBG("release 0x%04X\n", client->cid);
+ client_free(dev, client->cid);
+ }
+
+ qc_stopread(dev);
+ dev->valid = false;
+ list_for_each(inodes, &dev->qmi.cdev.list) {
+ inode = container_of(inodes, struct inode, i_devices);
+ if (inode != NULL && !IS_ERR(inode)) {
+ list_for_each(tasks, ¤t->group_leader->tasks) {
+ task = container_of(tasks, struct task_struct, tasks);
+ if (!task || !task->files)
+ continue;
+ spin_lock_irqsave(&task->files->file_lock, flags);
+ fdtable = files_fdtable(task->files);
+ for (count = 0; count < fdtable->max_fds; count++) {
+ file = fdtable->fd[count];
+ if (file != NULL && file->f_dentry != NULL) {
+ if (file->f_dentry->d_inode == inode) {
+ rcu_assign_pointer(fdtable->fd[count], NULL);
+ spin_unlock_irqrestore(&task->files->file_lock, flags);
+ DBG("forcing close of open file handle\n");
+ filp_close(file, task->files);
+ spin_lock_irqsave(&task->files->file_lock, flags);
+ }
+ }
+ }
+ spin_unlock_irqrestore(&task->files->file_lock, flags);
+ }
+ }
+ }
+
+ if (!IS_ERR(dev->qmi.devclass))
+ device_destroy(dev->qmi.devclass, dev->qmi.devnum);
+ cdev_del(&dev->qmi.cdev);
+ unregister_chrdev_region(dev->qmi.devnum, 1);
+}
+
+static bool qmi_ready(struct qcusbnet *dev, u16 timeout)
+{
+ int result;
+ void * wbuf;
+ size_t wbufsize;
+ void * rbuf;
+ u16 rbufsize;
+ struct semaphore sem;
+ u16 now;
+ unsigned long flags;
+ u8 tid;
+
+ if (!device_valid(dev)) {
+ DBG("Invalid device\n");
+ return -EFAULT;
+ }
+
+
+ for (now = 0; now < timeout; now += 100) {
+ sema_init(&sem, 0);
+
+ tid = atomic_add_return(1, &dev->qmi.qmitid);
+ if (!tid)
+ tid = atomic_add_return(1, &dev->qmi.qmitid);
+ if (wbuf)
+ kfree(wbuf);
+ wbuf = qmictl_new_ready(tid, &wbufsize);
+ if (!wbuf)
+ return -ENOMEM;
+
+ result = read_async(dev, QMICTL, tid, upsem, &sem);
+ if (result) {
+ kfree(wbuf);
+ return false;
+ }
+
+ write_sync(dev, wbuf, wbufsize, QMICTL);
+
+ msleep(100);
+ if (!down_trylock(&sem)) {
+ spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+ if (client_delread(dev, QMICTL, tid, &rbuf, &rbufsize)) {
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ kfree(rbuf);
+ break;
+ }
+ } else {
+ spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+ client_notify(dev, QMICTL, tid);
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+ }
+ }
+
+ if (wbuf)
+ kfree(wbuf);
+
+ if (now >= timeout)
+ return false;
+
+ DBG("QMI Ready after %u milliseconds\n", now);
+
+ /* 3580 and newer doesn't need a delay; older needs 5000ms */
+ if (qcusbnet2k_fwdelay)
+ msleep(qcusbnet2k_fwdelay * 1000);
+
+ return true;
+}
+
+static void wds_callback(struct qcusbnet *dev, u16 cid, void *data)
+{
+ bool ret;
+ int result;
+ void * rbuf;
+ u16 rbufsize;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
+ struct net_device_stats * stats = &(dev->usbnet->stats);
+#else
+ struct net_device_stats * stats = &(dev->usbnet->net->stats);
+#endif
+
+ struct qmiwds_stats dstats = {
+ .txok = (u32)-1,
+ .rxok = (u32)-1,
+ .txerr = (u32)-1,
+ .rxerr = (u32)-1,
+ .txofl = (u32)-1,
+ .rxofl = (u32)-1,
+ .txbytesok = (u64)-1,
+ .rxbytesok = (u64)-1,
+ };
+ unsigned long flags;
+
+ if (!device_valid(dev)) {
+ DBG("Invalid device\n");
+ return;
+ }
+
+ spin_lock_irqsave(&dev->qmi.clients_lock, flags);
+ ret = client_delread(dev, cid, 0, &rbuf, &rbufsize);
+ spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+
+ if (!ret) {
+ DBG("WDS callback failed to get data\n");
+ return;
+ }
+
+ dstats.linkstate = !qc_isdown(dev, DOWN_NO_NDIS_CONNECTION);
+ dstats.reconfigure = false;
+
+ result = qmiwds_event_resp(rbuf, rbufsize, &dstats);
+ if (result < 0) {
+ DBG("bad WDS packet\n");
+ } else {
+ if (dstats.txofl != (u32)-1)
+ stats->tx_fifo_errors = dstats.txofl;
+
+ if (dstats.rxofl != (u32)-1)
+ stats->rx_fifo_errors = dstats.rxofl;
+
+ if (dstats.txerr != (u32)-1)
+ stats->tx_errors = dstats.txerr;
+
+ if (dstats.rxerr != (u32)-1)
+ stats->rx_errors = dstats.rxerr;
+
+ if (dstats.txok != (u32)-1)
+ stats->tx_packets = dstats.txok + stats->tx_errors;
+
+ if (dstats.rxok != (u32)-1)
+ stats->rx_packets = dstats.rxok + stats->rx_errors;
+
+ if (dstats.txbytesok != (u64)-1)
+ stats->tx_bytes = dstats.txbytesok;
+
+ if (dstats.rxbytesok != (u64)-1)
+ stats->rx_bytes = dstats.rxbytesok;
+
+ if (dstats.reconfigure) {
+ DBG("Net device link reset\n");
+ qc_setdown(dev, DOWN_NO_NDIS_CONNECTION);
+ qc_cleardown(dev, DOWN_NO_NDIS_CONNECTION);
+ } else {
+ if (dstats.linkstate) {
+ DBG("Net device link is connected\n");
+ qc_cleardown(dev, DOWN_NO_NDIS_CONNECTION);
+ } else {
+ DBG("Net device link is disconnected\n");
+ qc_setdown(dev, DOWN_NO_NDIS_CONNECTION);
+ }
+ }
+ }
+
+ kfree(rbuf);
+
+ result = read_async(dev, cid, 0, wds_callback, data);
+ if (result != 0)
+ DBG("unable to setup next async read\n");
+}
+
+static int setup_wds_callback(struct qcusbnet * dev)
+{
+ int result;
+ void *buf;
+ size_t size;
+ u16 cid;
+
+ if (!device_valid(dev)) {
+ DBG("Invalid device\n");
+ return -EFAULT;
+ }
+
+ result = client_alloc(dev, QMIWDS);
+ if (result < 0)
+ return result;
+ cid = result;
+
+ buf = qmiwds_new_seteventreport(1, &size);
+ if (!buf)
+ return -ENOMEM;
+
+ result = write_sync(dev, buf, size, cid);
+ kfree(buf);
+
+ if (result < 0) {
+ return result;
+ }
+
+ buf = qmiwds_new_getpkgsrvcstatus(2, &size);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ result = write_sync(dev, buf, size, cid);
+ kfree(buf);
+
+ if (result < 0)
+ return result;
+
+ result = read_async(dev, cid, 0, wds_callback, NULL);
+ if (result) {
+ DBG("unable to setup async read\n");
+ return result;
+ }
+
+ result = usb_control_msg(dev->usbnet->udev,
+ usb_sndctrlpipe(dev->usbnet->udev, 0),
+ 0x22, 0x21, 1, 0, NULL, 0, 100);
+ if (result < 0) {
+ DBG("Bad SetControlLineState status %d\n", result);
+ return result;
+ }
+
+ return 0;
+}
+
+static int qmidms_getmeid(struct qcusbnet * dev)
+{
+ int result;
+ void * wbuf;
+ size_t wbufsize;
+ void * rbuf;
+ u16 rbufsize;
+ u16 cid;
+
+ if (!device_valid(dev)) {
+ DBG("Invalid device\n");
+ return -EFAULT;
+ }
+
+ result = client_alloc(dev, QMIDMS);
+ if (result < 0)
+ return result;
+ cid = result;
+
+ wbuf = qmidms_new_getmeid(1, &wbufsize);
+ if (!wbuf)
+ return -ENOMEM;
+
+ result = write_sync(dev, wbuf, wbufsize, cid);
+ kfree(wbuf);
+
+ if (result < 0)
+ return result;
+
+ result = read_sync(dev, &rbuf, cid, 1);
+ if (result < 0)
+ return result;
+ rbufsize = result;
+
+ result = qmidms_meid_resp(rbuf, rbufsize, &dev->meid[0], 14);
+ kfree(rbuf);
+
+ if (result < 0) {
+ DBG("bad get MEID resp\n");
+ memset(&dev->meid[0], '0', 14);
+ }
+
+ client_free(dev, cid);
+ return 0;
+}
+
+module_param(qcusbnet2k_fwdelay, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(qcusbnet2k_fwdelay, "Delay for old firmware");
diff --git a/drivers/net/usb/qcusbnet/qmidevice.h b/drivers/net/usb/qcusbnet/qmidevice.h
new file mode 100755
index 0000000..39a4b7f
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/qmidevice.h
@@ -0,0 +1,36 @@
+/* qmidevice.h - gobi QMI device header
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef QCUSBNET_QMIDEVICE_H
+#define QCUSBNET_QMIDEVICE_H
+
+#include "structs.h"
+#include "qmi.h"
+
+void printhex(const void *data, size_t size);
+void qc_setdown(struct qcusbnet *dev, u8 reason);
+void qc_cleardown(struct qcusbnet *dev, u8 reason);
+bool qc_isdown(struct qcusbnet *dev, u8 reason);
+
+int qc_startread(struct qcusbnet *dev);
+void qc_stopread(struct qcusbnet *dev);
+
+int qc_register(struct qcusbnet *dev);
+void qc_deregister(struct qcusbnet *dev);
+
+#endif /* !QCUSBNET_QMIDEVICE_H */
diff --git a/drivers/net/usb/qcusbnet/readme.txt b/drivers/net/usb/qcusbnet/readme.txt
new file mode 100755
index 0000000..cec6790
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/readme.txt
@@ -0,0 +1,118 @@
+Gobi2000 network driver for HP 1.0.110
+06/18/2010
+
+This readme covers important information concerning
+the QCUSBNet2kHP driver, provided in correlation with
+the Gobi2000-Linux-Package.
+
+Table of Contents
+
+1. Prerequisites
+2. Installation instructions
+3. What's new in this release
+4. Notes
+5. Known issues
+
+-------------------------------------------------------------------------------
+
+1. PREREQUISITES
+
+a. Kernel headers or full kernel source installed for the currently
+ running kernel. There must be a link "/lib/modules/<uname -r>/build"
+ that points to these kernel headers or sources.
+b. The kernel must support the usbcore and usbnet drivers, either as
+ modules or built into the kernel.
+c. Tools required for building the kernel. These tools usually come in a
+ package called "kernel-devel".
+d. "gcc" compiler
+e. "make" tool
+
+-------------------------------------------------------------------------------
+
+2. INSTALLATION INSTRUCTIONS
+
+a. Navigate to the folder "QCUSBNet2k" that contains:
+ Makefile
+ QCUSBNetHP.c
+ QMIDevice.c
+ qmidevice.h
+ structs.h
+ QMI.c
+ qmi.h
+b. (only required for kernels prior to 2.6.25) Create a symbolic link
+ to the usbnet.h file in your kernel sources:
+ ln -s <linux sources>/drivers/net/usb/usbnet.h ./
+c. Run the command:
+ make
+d. Copy the newly created QCUSBNet2kHP.ko into the directory
+ /lib/modules/`uname -r`/kernel/drivers/net/usb/
+e. Run the command:
+ depmod
+f. (Optional) Load the driver manually with the command:
+ modprobe QCUSBNet2kHP
+ - This is only required the first time after you install the
+ drivers. If you restart or plug the Gobi device in the drivers
+ will be automatically loaded.
+
+-------------------------------------------------------------------------------
+
+3. WHAT'S NEW
+
+This Release (Gobi2000 network driver for HP 1.0.110) 06/18/2010
+a. Correctly initialize semaphore during probe function.
+
+Prior Release (Gobi2000 network driver for HP 1.0.100) 06/02/2010
+a. Merge QCQMI driver into QCUSBNet2k driver, removing dependency.
+
+Prior Release (Gobi2000 network driver for HP 1.0.90) 05/13/2010
+a. Fix devqmi_close() from a thread
+b. Add 2.6.33 kernel support
+
+Prior Release (Gobi2000 network driver for HP 1.0.80) 04/19/2010
+a. Add support to check for duplicate or out of sequence QMI messages.
+
+Prior Release (Gobi2000 network driver for HP 1.0.70) 03/15/2010
+a. Added support for CDC CONNECTION_SPEED_CHANGE indication.
+b. Modified device cleanup function to better handle the device
+ losing power during suspend or hibernate.
+c. Replaced autosuspend feature with more aggressive "Selective suspend"
+ style power management. Even if QCWWAN2k or Network connections are
+ active the device will still enter autosuspend after the delay time,
+ and wake on remote wakeup or new data being sent.
+
+Prior Release (Gobi2000 Serial driver for HP 1.0.60) 02/16/2010
+a. Fix to allow proper fork() functionality
+b. Add supports_autosuspend flag
+c. Ignore EOVERFLOW errors on interrupt endpoint and resubmit URB
+d. Change driver versions to match installer package
+e. Minor update for 2.6.23 kernel compatibility
+
+Prior Release (in correlation with Gobi2000-Linux-Package 1.0.30) 12/04/2009
+a. Modify ioctl numbering to avoid conflict in 2.6.31 kernel
+ This release is only compatible with GOBI2000_LINUX_SDK 1.0.30 and newer.
+b. Made minor compatibility changes for 2.6.31 kernel
+
+Prior Release (in correlation with Gobi2000-Linux-Package 1.0.20) 11/20/2009
+a. Initial release
+
+-------------------------------------------------------------------------------
+
+4. NOTES
+
+a. In Composite mode, the Gobi device will enumerate a network interface
+ usb<#> where <#> signifies the next available USB network interface.
+b. In Composite mode, the Gobi device will enumerate a device node
+ /dev/qcqmi<#>. This device node is for use by the QCWWANCMAPI2k.
+c. Ownership, group, and permissions are managed by your system
+ configuration.
+
+-------------------------------------------------------------------------------
+
+5. KNOWN ISSUES
+
+No known issues.
+
+-------------------------------------------------------------------------------
+
+
+
diff --git a/drivers/net/usb/qcusbnet/structs.h b/drivers/net/usb/qcusbnet/structs.h
new file mode 100755
index 0000000..0f9f4eb
--- /dev/null
+++ b/drivers/net/usb/qcusbnet/structs.h
@@ -0,0 +1,99 @@
+/* structs.h - shared structures
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef QCUSBNET_STRUCTS_H
+#define QCUSBNET_STRUCTS_H
+
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+#include <linux/cdev.h>
+#include <linux/kthread.h>
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,24)
+ #include "usbnet.h"
+#else
+ #include <linux/usb/usbnet.h>
+#endif
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,25)
+ #include <linux/fdtable.h>
+#else
+ #include <linux/file.h>
+#endif
+
+#define DBG(format, arg...) \
+ if (debug == 1) { \
+ printk(KERN_INFO "QCUSBNet2k::%s " format, __FUNCTION__, ## arg); \
+ }
+
+struct qcusbnet;
+
+struct urbreq {
+ struct list_head node;
+ struct urb *urb;
+};
+
+#define DEFAULT_READ_URB_LENGTH 0x1000
+
+struct worker {
+ struct task_struct *thread;
+ struct completion work;
+ struct list_head urbs;
+ spinlock_t urbs_lock;
+ struct urb *active;
+ spinlock_t active_lock;
+ struct usb_interface *iface;
+};
+
+struct qmidev {
+ dev_t devnum;
+ struct class *devclass;
+ struct cdev cdev;
+ struct urb *readurb;
+ struct urbsetup *readsetup;
+ void *readbuf;
+ struct urb *inturb;
+ void *intbuf;
+ struct list_head clients;
+ spinlock_t clients_lock;
+ atomic_t qmitid;
+};
+
+enum {
+ DOWN_NO_NDIS_CONNECTION = 0,
+ DOWN_CDC_CONNECTION_SPEED = 1,
+ DOWN_DRIVER_SUSPENDED = 2,
+ DOWN_NET_IFACE_STOPPED = 3,
+};
+
+struct qcusbnet {
+ struct usbnet *usbnet;
+ struct usb_interface *iface;
+ int (*open)(struct net_device *);
+ int (*stop)(struct net_device *);
+ unsigned long down;
+ bool valid;
+ struct qmidev qmi;
+ char meid[14];
+ struct worker worker;
+};
+
+#endif /* !QCUSBNET_STRUCTS_H */
--
1.7.1
^ permalink raw reply related
* [PATCH -next] pch_gbe: add header files
From: Randy Dunlap @ 2010-09-28 16:56 UTC (permalink / raw)
To: Stephen Rothwell, netdev, davem; +Cc: linux-next, LKML, masa-korg
In-Reply-To: <20100928141823.8c61000e.sfr@canb.auug.org.au>
From: Randy Dunlap <randy.dunlap@oracle.com>
Fix build errors, more header files needed.
drivers/net/pch_gbe/pch_gbe_main.c:965: error: implicit declaration of function 'tcp_hdr'
drivers/net/pch_gbe/pch_gbe_main.c:965: error: invalid type argument of '->' (have 'int')
drivers/net/pch_gbe/pch_gbe_main.c:968: error: invalid type argument of '->' (have 'int')
drivers/net/pch_gbe/pch_gbe_main.c:976: error: implicit declaration of function 'udp_hdr'
drivers/net/pch_gbe/pch_gbe_main.c:976: error: invalid type argument of '->' (have 'int')
drivers/net/pch_gbe/pch_gbe_main.c:980: error: invalid type argument of '->' (have 'int')
Signed-off-by: Randy Dunlap <randy.dunlap@oracle.com>
---
drivers/net/pch_gbe/pch_gbe.h | 2 ++
1 file changed, 2 insertions(+)
--- linux-next-20100927.orig/drivers/net/pch_gbe/pch_gbe.h
+++ linux-next-20100927/drivers/net/pch_gbe/pch_gbe.h
@@ -31,6 +31,8 @@
#include <linux/ethtool.h>
#include <linux/vmalloc.h>
#include <net/ip.h>
+#include <net/tcp.h>
+#include <net/udp.h>
/**
* pch_gbe_regs_mac_adr - Structure holding values of mac address registers
^ permalink raw reply
* Re: [PATCH] ipv4: remove all rt cache entries on UNREGISTER event
From: Eric Dumazet @ 2010-09-28 16:56 UTC (permalink / raw)
To: nicolas.dichtel; +Cc: netdev, Octavian Purdila
In-Reply-To: <4CA21BC6.5070300@6wind.com>
Le mardi 28 septembre 2010 à 18:45 +0200, Nicolas Dichtel a écrit :
> Eric Dumazet wrote:
> > Le mardi 28 septembre 2010 à 17:24 +0200, Nicolas Dichtel a écrit :
> >> Hi,
> >>
> >> I face a problem when I try to remove an interface,
> >> netdev_wait_allrefs() complains about refcount.
> >>
> >> Here is a trivial scenario to reproduce the problem:
> >> # ip tunnel add mode ipip remote 10.16.0.164 local 10.16.0.72 dev eth0
> >> # ./a.out tunl1
> >> # ip tunnel del tunl1
> >>
> >> Note: a.out binary create an IPv4 raw socket, attach it to tunl1
> >> (SO_BINDTODEVICE), set it as multicast (IP_MULTICAST_LOOP), set the
> >> multicast interface to tunl1 (IP_MULTICAST_IF), build the IP header
> >> (IP_HDRINCL) and then send a single packet (192.168.6.1 -> 224.0.0.18).
> >>
> >> Note2: when a.out is executed, tunl1 has no ip address and is down.
> >>
> >
> > CC Octavian Purdila, the patch author.
> >
> > I am just wondering why this route is created in the first place.
> At first, I asked myself the same question, but it seems that this is
> allowed to send a packet through this kind of socket, even if interface
> is down. Packet will be destroyed by the noop qdisk.
> But I agree that it is strange to perform route lookup and everything to
> destroy the packet at the end ...
> Maybe raw_sendmsg() can delete it directly ;-) ... or maybe
> ip_route_output_flow().
>
> Any suggestions welcome.
>
Hmm...
One way to track this kind of problem would be to add a WARN_ON() in
dev_hold()
-> Check that when a reference on dev is taken, we are in a known state.
Something like this ?
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 83de0eb..54bef78 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1773,6 +1774,7 @@ static inline void dev_put(struct net_device *dev)
*/
static inline void dev_hold(struct net_device *dev)
{
+ WARN_ON(dev->reg_state != NETREG_REGISTERED);
atomic_inc(&dev->refcnt);
}
^ permalink raw reply related
* Re: [PATCH] ipv4: remove all rt cache entries on UNREGISTER event
From: Nicolas Dichtel @ 2010-09-28 16:45 UTC (permalink / raw)
To: Eric Dumazet; +Cc: netdev, Octavian Purdila
In-Reply-To: <1285691629.3154.80.camel@edumazet-laptop>
Eric Dumazet wrote:
> Le mardi 28 septembre 2010 à 17:24 +0200, Nicolas Dichtel a écrit :
>> Hi,
>>
>> I face a problem when I try to remove an interface,
>> netdev_wait_allrefs() complains about refcount.
>>
>> Here is a trivial scenario to reproduce the problem:
>> # ip tunnel add mode ipip remote 10.16.0.164 local 10.16.0.72 dev eth0
>> # ./a.out tunl1
>> # ip tunnel del tunl1
>>
>> Note: a.out binary create an IPv4 raw socket, attach it to tunl1
>> (SO_BINDTODEVICE), set it as multicast (IP_MULTICAST_LOOP), set the
>> multicast interface to tunl1 (IP_MULTICAST_IF), build the IP header
>> (IP_HDRINCL) and then send a single packet (192.168.6.1 -> 224.0.0.18).
>>
>> Note2: when a.out is executed, tunl1 has no ip address and is down.
>>
>
> CC Octavian Purdila, the patch author.
>
> I am just wondering why this route is created in the first place.
At first, I asked myself the same question, but it seems that this is
allowed to send a packet through this kind of socket, even if interface
is down. Packet will be destroyed by the noop qdisk.
But I agree that it is strange to perform route lookup and everything to
destroy the packet at the end ...
Maybe raw_sendmsg() can delete it directly ;-) ... or maybe
ip_route_output_flow().
Any suggestions welcome.
Regards,
Nicolas
>
> Maybe a fix would be to forbid this ?
>
> Some machines have a giant route cache, so its very important to avoid
> expensive scans.
>
>> Then, I got a serie of "kernel:[1206699.728010] unregister_netdevice:
>> waiting for tunl1 to become free. Usage count = 3" and after some time,
>> interface is removed.
>>
>> The problem is that route cache entries are only invalidate on
>> UNREGISTER event, and not removed (introduced by commit
>> e2ce146848c81af2f6d42e67990191c284bf0c33). We must wait that
>> rt_check_expire() remove the remaining route cache entries.
>>
>> To fix the problem, I propose to remove a part of the previous commit.
>>
>> Regards,
>> Nicolas
>> pièce jointe différences entre fichiers
>> (0001-ipv4-remove-all-rt-cache-entries-on-UNREGISTER-even.patch)
>> From 3344e2e0431fe803c4dac8757a8746908357d780 Mon Sep 17 00:00:00 2001
>> From: Nicolas Dichtel <nicolas.dichtel@6wind.com>
>> Date: Tue, 28 Sep 2010 16:38:19 +0200
>> Subject: [PATCH] ipv4: remove all rt cache entries on UNREGISTER event
>>
>> Commit e2ce146848c81af2f6d42e67990191c284bf0c33 (ipv4: factorize cache clearing
>> for batched unregister operations) add a new parameter to fib_disable_ip() to
>> only invalidate route cache entries on unregister event.
>> This is wrong, we should ensure that all cache entries are removed on
>> unregister event, else netdev_wait_allrefs() may complain. A cache entry
>> can be created between event DOWN and UNREGISTER.
>>
>> So, I revert a part of the patch.
>>
>> Signed-off-by: Nicolas Dichtel <nicolas.dichtel@6wind.com>
>> ---
>> net/ipv4/fib_frontend.c | 10 +++++-----
>> 1 files changed, 5 insertions(+), 5 deletions(-)
>>
>> diff --git a/net/ipv4/fib_frontend.c b/net/ipv4/fib_frontend.c
>> index 7d02a9f..377e815 100644
>> --- a/net/ipv4/fib_frontend.c
>> +++ b/net/ipv4/fib_frontend.c
>> @@ -917,11 +917,11 @@ static void nl_fib_lookup_exit(struct net *net)
>> net->ipv4.fibnl = NULL;
>> }
>>
>> -static void fib_disable_ip(struct net_device *dev, int force, int delay)
>> +static void fib_disable_ip(struct net_device *dev, int force)
>> {
>> if (fib_sync_down_dev(dev, force))
>> fib_flush(dev_net(dev));
>> - rt_cache_flush(dev_net(dev), delay);
>> + rt_cache_flush(dev_net(dev), 0);
>> arp_ifdown(dev);
>> }
>>
>> @@ -944,7 +944,7 @@ static int fib_inetaddr_event(struct notifier_block *this, unsigned long event,
>> /* Last address was deleted from this interface.
>> Disable IP.
>> */
>> - fib_disable_ip(dev, 1, 0);
>> + fib_disable_ip(dev, 1);
>> } else {
>> rt_cache_flush(dev_net(dev), -1);
>> }
>> @@ -959,7 +959,7 @@ static int fib_netdev_event(struct notifier_block *this, unsigned long event, vo
>> struct in_device *in_dev = __in_dev_get_rtnl(dev);
>>
>> if (event == NETDEV_UNREGISTER) {
>> - fib_disable_ip(dev, 2, -1);
>> + fib_disable_ip(dev, 2);
>> return NOTIFY_DONE;
>> }
>>
>> @@ -977,7 +977,7 @@ static int fib_netdev_event(struct notifier_block *this, unsigned long event, vo
>> rt_cache_flush(dev_net(dev), -1);
>> break;
>> case NETDEV_DOWN:
>> - fib_disable_ip(dev, 0, 0);
>> + fib_disable_ip(dev, 0);
>> break;
>> case NETDEV_CHANGEMTU:
>> case NETDEV_CHANGE:
>
>
^ permalink raw reply
* Re: [PATCH] ipv4: remove all rt cache entries on UNREGISTER event
From: Eric Dumazet @ 2010-09-28 16:33 UTC (permalink / raw)
To: nicolas.dichtel; +Cc: netdev, Octavian Purdila
In-Reply-To: <4CA208C9.1020800@6wind.com>
Le mardi 28 septembre 2010 à 17:24 +0200, Nicolas Dichtel a écrit :
> Hi,
>
> I face a problem when I try to remove an interface,
> netdev_wait_allrefs() complains about refcount.
>
> Here is a trivial scenario to reproduce the problem:
> # ip tunnel add mode ipip remote 10.16.0.164 local 10.16.0.72 dev eth0
> # ./a.out tunl1
> # ip tunnel del tunl1
>
> Note: a.out binary create an IPv4 raw socket, attach it to tunl1
> (SO_BINDTODEVICE), set it as multicast (IP_MULTICAST_LOOP), set the
> multicast interface to tunl1 (IP_MULTICAST_IF), build the IP header
> (IP_HDRINCL) and then send a single packet (192.168.6.1 -> 224.0.0.18).
>
> Note2: when a.out is executed, tunl1 has no ip address and is down.
>
CC Octavian Purdila, the patch author.
I am just wondering why this route is created in the first place.
Maybe a fix would be to forbid this ?
Some machines have a giant route cache, so its very important to avoid
expensive scans.
> Then, I got a serie of "kernel:[1206699.728010] unregister_netdevice:
> waiting for tunl1 to become free. Usage count = 3" and after some time,
> interface is removed.
>
> The problem is that route cache entries are only invalidate on
> UNREGISTER event, and not removed (introduced by commit
> e2ce146848c81af2f6d42e67990191c284bf0c33). We must wait that
> rt_check_expire() remove the remaining route cache entries.
>
> To fix the problem, I propose to remove a part of the previous commit.
>
> Regards,
> Nicolas
> pièce jointe différences entre fichiers
> (0001-ipv4-remove-all-rt-cache-entries-on-UNREGISTER-even.patch)
> From 3344e2e0431fe803c4dac8757a8746908357d780 Mon Sep 17 00:00:00 2001
> From: Nicolas Dichtel <nicolas.dichtel@6wind.com>
> Date: Tue, 28 Sep 2010 16:38:19 +0200
> Subject: [PATCH] ipv4: remove all rt cache entries on UNREGISTER event
>
> Commit e2ce146848c81af2f6d42e67990191c284bf0c33 (ipv4: factorize cache clearing
> for batched unregister operations) add a new parameter to fib_disable_ip() to
> only invalidate route cache entries on unregister event.
> This is wrong, we should ensure that all cache entries are removed on
> unregister event, else netdev_wait_allrefs() may complain. A cache entry
> can be created between event DOWN and UNREGISTER.
>
> So, I revert a part of the patch.
>
> Signed-off-by: Nicolas Dichtel <nicolas.dichtel@6wind.com>
> ---
> net/ipv4/fib_frontend.c | 10 +++++-----
> 1 files changed, 5 insertions(+), 5 deletions(-)
>
> diff --git a/net/ipv4/fib_frontend.c b/net/ipv4/fib_frontend.c
> index 7d02a9f..377e815 100644
> --- a/net/ipv4/fib_frontend.c
> +++ b/net/ipv4/fib_frontend.c
> @@ -917,11 +917,11 @@ static void nl_fib_lookup_exit(struct net *net)
> net->ipv4.fibnl = NULL;
> }
>
> -static void fib_disable_ip(struct net_device *dev, int force, int delay)
> +static void fib_disable_ip(struct net_device *dev, int force)
> {
> if (fib_sync_down_dev(dev, force))
> fib_flush(dev_net(dev));
> - rt_cache_flush(dev_net(dev), delay);
> + rt_cache_flush(dev_net(dev), 0);
> arp_ifdown(dev);
> }
>
> @@ -944,7 +944,7 @@ static int fib_inetaddr_event(struct notifier_block *this, unsigned long event,
> /* Last address was deleted from this interface.
> Disable IP.
> */
> - fib_disable_ip(dev, 1, 0);
> + fib_disable_ip(dev, 1);
> } else {
> rt_cache_flush(dev_net(dev), -1);
> }
> @@ -959,7 +959,7 @@ static int fib_netdev_event(struct notifier_block *this, unsigned long event, vo
> struct in_device *in_dev = __in_dev_get_rtnl(dev);
>
> if (event == NETDEV_UNREGISTER) {
> - fib_disable_ip(dev, 2, -1);
> + fib_disable_ip(dev, 2);
> return NOTIFY_DONE;
> }
>
> @@ -977,7 +977,7 @@ static int fib_netdev_event(struct notifier_block *this, unsigned long event, vo
> rt_cache_flush(dev_net(dev), -1);
> break;
> case NETDEV_DOWN:
> - fib_disable_ip(dev, 0, 0);
> + fib_disable_ip(dev, 0);
> break;
> case NETDEV_CHANGEMTU:
> case NETDEV_CHANGE:
^ permalink raw reply
* [PATCH net-next2.6] net: rename netdev rx_queue to ingress_queue
From: Eric Dumazet @ 2010-09-28 15:58 UTC (permalink / raw)
To: David Miller; +Cc: netdev, Jarek Poplawski
There is some confusion with rx_queue name after RPS, and net drivers
private rx_queue fields.
I suggest to rename "struct net_device"->rx_queue to ingress_queue.
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
---
My plan is to make this structure dynamically allocated, in the rare
case ingress is setup, to shrink net_device.
include/linux/netdevice.h | 2 +-
net/core/dev.c | 8 ++++----
net/sched/sch_api.c | 14 +++++++-------
net/sched/sch_generic.c | 8 ++++----
4 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 83de0eb..1ae0d65 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -983,7 +983,7 @@ struct net_device {
rx_handler_func_t *rx_handler;
void *rx_handler_data;
- struct netdev_queue rx_queue; /* use two cache lines */
+ struct netdev_queue ingress_queue; /* use two cache lines */
/*
* Cache lines mostly used on transmit path
diff --git a/net/core/dev.c b/net/core/dev.c
index 42b200f..0ed0669 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -2674,7 +2674,7 @@ static int ing_filter(struct sk_buff *skb)
skb->tc_verd = SET_TC_RTTL(skb->tc_verd, ttl);
skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_INGRESS);
- rxq = &dev->rx_queue;
+ rxq = &dev->ingress_queue;
q = rxq->qdisc;
if (q != &noop_qdisc) {
@@ -2691,7 +2691,7 @@ static inline struct sk_buff *handle_ing(struct sk_buff *skb,
struct packet_type **pt_prev,
int *ret, struct net_device *orig_dev)
{
- if (skb->dev->rx_queue.qdisc == &noop_qdisc)
+ if (skb->dev->ingress_queue.qdisc == &noop_qdisc)
goto out;
if (*pt_prev) {
@@ -4894,7 +4894,7 @@ static void __netdev_init_queue_locks_one(struct net_device *dev,
static void netdev_init_queue_locks(struct net_device *dev)
{
netdev_for_each_tx_queue(dev, __netdev_init_queue_locks_one, NULL);
- __netdev_init_queue_locks_one(dev, &dev->rx_queue, NULL);
+ __netdev_init_queue_locks_one(dev, &dev->ingress_queue, NULL);
}
unsigned long netdev_fix_features(unsigned long features, const char *name)
@@ -5406,7 +5406,7 @@ static void netdev_init_one_queue(struct net_device *dev,
static void netdev_init_queues(struct net_device *dev)
{
- netdev_init_one_queue(dev, &dev->rx_queue, NULL);
+ netdev_init_one_queue(dev, &dev->ingress_queue, NULL);
netdev_for_each_tx_queue(dev, netdev_init_one_queue, NULL);
spin_lock_init(&dev->tx_global_lock);
}
diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c
index 6fb3d41..b802078 100644
--- a/net/sched/sch_api.c
+++ b/net/sched/sch_api.c
@@ -240,7 +240,7 @@ struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle)
if (q)
goto out;
- q = qdisc_match_from_root(dev->rx_queue.qdisc_sleeping, handle);
+ q = qdisc_match_from_root(dev->ingress_queue.qdisc_sleeping, handle);
out:
return q;
}
@@ -701,7 +701,7 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
}
for (i = 0; i < num_q; i++) {
- struct netdev_queue *dev_queue = &dev->rx_queue;
+ struct netdev_queue *dev_queue = &dev->ingress_queue;
if (!ingress)
dev_queue = netdev_get_tx_queue(dev, i);
@@ -979,7 +979,7 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
return -ENOENT;
q = qdisc_leaf(p, clid);
} else { /* ingress */
- q = dev->rx_queue.qdisc_sleeping;
+ q = dev->ingress_queue.qdisc_sleeping;
}
} else {
q = dev->qdisc;
@@ -1044,7 +1044,7 @@ replay:
return -ENOENT;
q = qdisc_leaf(p, clid);
} else { /*ingress */
- q = dev->rx_queue.qdisc_sleeping;
+ q = dev->ingress_queue.qdisc_sleeping;
}
} else {
q = dev->qdisc;
@@ -1124,7 +1124,7 @@ create_n_graft:
if (!(n->nlmsg_flags&NLM_F_CREATE))
return -ENOENT;
if (clid == TC_H_INGRESS)
- q = qdisc_create(dev, &dev->rx_queue, p,
+ q = qdisc_create(dev, &dev->ingress_queue, p,
tcm->tcm_parent, tcm->tcm_parent,
tca, &err);
else {
@@ -1304,7 +1304,7 @@ static int tc_dump_qdisc(struct sk_buff *skb, struct netlink_callback *cb)
if (tc_dump_qdisc_root(dev->qdisc, skb, cb, &q_idx, s_q_idx) < 0)
goto done;
- dev_queue = &dev->rx_queue;
+ dev_queue = &dev->ingress_queue;
if (tc_dump_qdisc_root(dev_queue->qdisc_sleeping, skb, cb, &q_idx, s_q_idx) < 0)
goto done;
@@ -1595,7 +1595,7 @@ static int tc_dump_tclass(struct sk_buff *skb, struct netlink_callback *cb)
if (tc_dump_tclass_root(dev->qdisc, skb, tcm, cb, &t, s_t) < 0)
goto done;
- dev_queue = &dev->rx_queue;
+ dev_queue = &dev->ingress_queue;
if (tc_dump_tclass_root(dev_queue->qdisc_sleeping, skb, tcm, cb, &t, s_t) < 0)
goto done;
diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c
index 2aeb3a4..545278a 100644
--- a/net/sched/sch_generic.c
+++ b/net/sched/sch_generic.c
@@ -753,7 +753,7 @@ void dev_activate(struct net_device *dev)
need_watchdog = 0;
netdev_for_each_tx_queue(dev, transition_one_qdisc, &need_watchdog);
- transition_one_qdisc(dev, &dev->rx_queue, NULL);
+ transition_one_qdisc(dev, &dev->ingress_queue, NULL);
if (need_watchdog) {
dev->trans_start = jiffies;
@@ -812,7 +812,7 @@ static bool some_qdisc_is_busy(struct net_device *dev)
void dev_deactivate(struct net_device *dev)
{
netdev_for_each_tx_queue(dev, dev_deactivate_queue, &noop_qdisc);
- dev_deactivate_queue(dev, &dev->rx_queue, &noop_qdisc);
+ dev_deactivate_queue(dev, &dev->ingress_queue, &noop_qdisc);
dev_watchdog_down(dev);
@@ -838,7 +838,7 @@ void dev_init_scheduler(struct net_device *dev)
{
dev->qdisc = &noop_qdisc;
netdev_for_each_tx_queue(dev, dev_init_scheduler_queue, &noop_qdisc);
- dev_init_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);
+ dev_init_scheduler_queue(dev, &dev->ingress_queue, &noop_qdisc);
setup_timer(&dev->watchdog_timer, dev_watchdog, (unsigned long)dev);
}
@@ -861,7 +861,7 @@ static void shutdown_scheduler_queue(struct net_device *dev,
void dev_shutdown(struct net_device *dev)
{
netdev_for_each_tx_queue(dev, shutdown_scheduler_queue, &noop_qdisc);
- shutdown_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);
+ shutdown_scheduler_queue(dev, &dev->ingress_queue, &noop_qdisc);
qdisc_destroy(dev->qdisc);
dev->qdisc = &noop_qdisc;
^ permalink raw reply related
* [PATCHv2 net-next-2.6 5/5] XFRM,IPv6: Add IRO remapping capability via socket ancillary data path
From: Arnaud Ebalard @ 2010-09-28 15:54 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Herbert Xu, Hideaki YOSHIFUJI; +Cc: netdev
In-Reply-To: <cover.1285687738.git.arno@natisbad.org>
This provides the ability to remap src/dst address using IRO
via ancillary data passed to sockets. This is the IRO equivalent
of what is done for RH2/HAO (i.e. IPV6_RTHDR/IPV6_DSTOPTS).
This is used by UMIP during BA emission when acting as a Home
Agent.
Signed-off-by: Arnaud Ebalard <arno@natisbad.org>
---
include/net/ipv6.h | 4 ++++
net/ipv6/datagram.c | 20 ++++++++++++++++++++
net/ipv6/exthdrs.c | 19 +++++++++++++------
net/ipv6/ip6_flowlabel.c | 7 +++++++
net/ipv6/ip6_output.c | 22 ++++++++++++++++++++++
net/ipv6/raw.c | 3 ++-
6 files changed, 68 insertions(+), 7 deletions(-)
diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index 4a3cd2c..2ba96d8 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -188,6 +188,10 @@ struct ipv6_txoptions {
struct ipv6_rt_hdr *srcrt; /* Routing Header */
struct ipv6_opt_hdr *dst1opt;
+ /* XXX protect those via some ifdef e.g. CONFIG_XFRM_SUB_POLICY ? */
+ struct in6_addr *iro_src;
+ struct in6_addr *iro_dst;
+
/* Option buffer, as read by IPV6_PKTOPTIONS, starts here. */
};
diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c
index 2952c9e..0ac7adf 100644
--- a/net/ipv6/datagram.c
+++ b/net/ipv6/datagram.c
@@ -757,6 +757,26 @@ int datagram_send_ctl(struct net *net,
}
break;
+#ifdef CONFIG_XFRM_SUB_POLICY
+ case IPV6_IROSRC:
+ case IPV6_IRODST:
+ if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct in6_addr))) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+
+ if (!capable(CAP_NET_RAW)) {
+ err = -EPERM;
+ goto exit_f;
+ }
+
+ if (cmsg->cmsg_type == IPV6_IROSRC)
+ opt->iro_src = (struct in6_addr *)CMSG_DATA(cmsg);
+ else
+ opt->iro_dst = (struct in6_addr *)CMSG_DATA(cmsg);
+ break;
+#endif
+
case IPV6_2292RTHDR:
case IPV6_RTHDR:
if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_rt_hdr))) {
diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index 262f105..e480b06 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -750,6 +750,10 @@ ipv6_dup_options(struct sock *sk, struct ipv6_txoptions *opt)
*((char**)&opt2->dst1opt) += dif;
if (opt2->srcrt)
*((char**)&opt2->srcrt) += dif;
+ if (opt2->iro_src)
+ *((char**)&opt2->iro_src) += dif;
+ if (opt2->iro_dst)
+ *((char**)&opt2->iro_dst) += dif;
}
return opt2;
}
@@ -874,24 +878,27 @@ struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space,
/**
* fl6_update_dst - update flowi destination address with info given
- * by srcrt option, if any.
+ * by srcrt/iro_dst option, if any.
*
* @fl: flowi for which fl6_dst is to be updated
- * @opt: struct ipv6_txoptions in which to look for srcrt opt
+ * @opt: struct ipv6_txoptions in which to look for srcrt/iro_dst opt
* @orig: copy of original fl6_dst address if modified
*
- * Returns NULL if no txoptions or no srcrt, otherwise returns orig
- * and initial value of fl->fl6_dst set in orig
+ * Returns NULL if no txoptions or no options to change flowi destination
+ * (srcrt or IRO destination remapping rule), otherwise returns orig and
+ * initial value of fl->fl6_dst set in orig
*/
struct in6_addr *fl6_update_dst(struct flowi *fl,
const struct ipv6_txoptions *opt,
struct in6_addr *orig)
{
- if (!opt || !opt->srcrt)
+ if (!opt || (!opt->srcrt && !opt->iro_dst))
return NULL;
ipv6_addr_copy(orig, &fl->fl6_dst);
- ipv6_addr_copy(&fl->fl6_dst, ((struct rt0_hdr *)opt->srcrt)->addr);
+ ipv6_addr_copy(&fl->fl6_dst,
+ opt->iro_dst ?: ((struct rt0_hdr *)opt->srcrt)->addr);
+
return orig;
}
diff --git a/net/ipv6/ip6_flowlabel.c b/net/ipv6/ip6_flowlabel.c
index 1365468..dbf9c29 100644
--- a/net/ipv6/ip6_flowlabel.c
+++ b/net/ipv6/ip6_flowlabel.c
@@ -280,6 +280,9 @@ struct ipv6_txoptions *fl6_merge_options(struct ipv6_txoptions * opt_space,
opt_space->hopopt = fl_opt->hopopt;
opt_space->dst0opt = fl_opt->dst0opt;
opt_space->srcrt = fl_opt->srcrt;
+ /* XXX protect those via some ifdef - see net/ipv6.h */
+ opt_space->iro_src = fl_opt->iro_src;
+ opt_space->iro_dst = fl_opt->iro_dst;
opt_space->opt_nflen = fl_opt->opt_nflen;
} else {
if (fopt->opt_nflen == 0)
@@ -287,6 +290,9 @@ struct ipv6_txoptions *fl6_merge_options(struct ipv6_txoptions * opt_space,
opt_space->hopopt = NULL;
opt_space->dst0opt = NULL;
opt_space->srcrt = NULL;
+ /* XXX protect those via some ifdef - see net/ipv6.h */
+ opt_space->iro_src = NULL;
+ opt_space->iro_dst = NULL;
opt_space->opt_nflen = 0;
}
opt_space->dst1opt = fopt->dst1opt;
@@ -456,6 +462,7 @@ static int ipv6_opt_cmp(struct ipv6_txoptions *o1, struct ipv6_txoptions *o2)
return 1;
if (ipv6_hdr_cmp((struct ipv6_opt_hdr *)o1->srcrt, (struct ipv6_opt_hdr *)o2->srcrt))
return 1;
+
return 0;
}
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 99157b4..210f269 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -222,6 +222,8 @@ int ip6_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl,
ipv6_push_frag_opts(skb, opt, &proto);
if (opt->opt_nflen)
ipv6_push_nfrag_opts(skb, opt, &proto, &first_hop);
+ if (opt->iro_dst)
+ first_hop = opt->iro_dst;
}
skb_push(skb, sizeof(struct ipv6hdr));
@@ -1106,6 +1108,12 @@ static inline struct ipv6_rt_hdr *ip6_rthdr_dup(struct ipv6_rt_hdr *src,
return src ? kmemdup(src, (src->hdrlen + 1) * 8, gfp) : NULL;
}
+static inline struct in6_addr *ip6_iro_addr_dup(struct in6_addr *addr,
+ gfp_t gfp)
+{
+ return addr ? kmemdup(addr, sizeof(struct in6_addr), gfp) : NULL;
+}
+
int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
int offset, int len, int odd, struct sk_buff *skb),
void *from, int length, int transhdrlen,
@@ -1162,6 +1170,16 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
if (opt->srcrt && !np->cork.opt->srcrt)
return -ENOBUFS;
+ np->cork.opt->iro_src = ip6_iro_addr_dup(opt->iro_src,
+ sk->sk_allocation);
+ if (opt->iro_src && !np->cork.opt->iro_src)
+ return -ENOBUFS;
+
+ np->cork.opt->iro_dst = ip6_iro_addr_dup(opt->iro_dst,
+ sk->sk_allocation);
+ if (opt->iro_dst && !np->cork.opt->iro_dst)
+ return -ENOBUFS;
+
/* need source address above miyazawa*/
}
dst_hold(&rt->dst);
@@ -1440,6 +1458,8 @@ static void ip6_cork_release(struct inet_sock *inet, struct ipv6_pinfo *np)
kfree(np->cork.opt->dst1opt);
kfree(np->cork.opt->hopopt);
kfree(np->cork.opt->srcrt);
+ kfree(np->cork.opt->iro_src);
+ kfree(np->cork.opt->iro_dst);
kfree(np->cork.opt);
np->cork.opt = NULL;
}
@@ -1495,6 +1515,8 @@ int ip6_push_pending_frames(struct sock *sk)
ipv6_push_frag_opts(skb, opt, &proto);
if (opt && opt->opt_nflen)
ipv6_push_nfrag_opts(skb, opt, &proto, &final_dst);
+ if (opt && opt->iro_dst)
+ final_dst = opt->iro_dst;
skb_push(skb, sizeof(struct ipv6hdr));
skb_reset_network_header(skb);
diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c
index 45e6efb..1a11bd5 100644
--- a/net/ipv6/raw.c
+++ b/net/ipv6/raw.c
@@ -828,7 +828,8 @@ static int rawv6_sendmsg(struct kiocb *iocb, struct sock *sk,
if (flowlabel == NULL)
return -EINVAL;
}
- if (!(opt->opt_nflen|opt->opt_flen))
+ if (!(opt->opt_nflen|opt->opt_flen) &&
+ (!opt->iro_src && !opt->iro_dst))
opt = NULL;
}
if (opt == NULL)
--
1.7.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox