Netdev List
 help / color / mirror / Atom feed
* Re: ixgbe: normalize frag_list usage
From: David Miller @ 2010-10-04 18:32 UTC (permalink / raw)
  To: alexander.h.duyck
  Cc: jeffrey.t.kirsher, jesse.brandeburg, bruce.w.allan, netdev
In-Reply-To: <4CAA1722.7070405@intel.com>

From: Alexander Duyck <alexander.h.duyck@intel.com>
Date: Mon, 04 Oct 2010 11:04:18 -0700

> This will not work for RSC due to the fact that it assumes only one
> RSC context is active per ring and that is not the case.  There can be
> multiple RSC combined flows interlaced on the ring.

Thanks for looking at this Alexander.

I must have mis-understood what the current code is doing.

It looked like RSC packets always show up in-order in the RX ring.

And that they are scanned for by simply combining any sequence of
non-EOP packets up to and including the final EOP one into a frag
list.

Are the RSC packets are identified via something else entirely?

^ permalink raw reply

* Re: [PATCH] AF_UNIX: Implement SO_TIMESTAMP and SO_TIMETAMPNS on Unix sockets
From: Eric Dumazet @ 2010-10-04 18:09 UTC (permalink / raw)
  To: Alban Crequy
  Cc: Lennart Poettering, David S. Miller, Stephen Hemminger,
	Cyrill Gorcunov, Alexey Dobriyan, linux-kernel, netdev
In-Reply-To: <1286215294-29166-1-git-send-email-alban.crequy@collabora.co.uk>

Le lundi 04 octobre 2010 à 19:01 +0100, Alban Crequy a écrit :
> Userspace applications can already request to receive timestamps with:
> setsockopt(sockfd, SOL_SOCKET, SO_TIMESTAMP, ...)
> 
> Although setsockopt() returns zero (success), timestamps are not added to the
> ancillary data. This patch fixes that on SOCK_DGRAM and SOCK_SEQPACKET Unix
> sockets.
> 
> Signed-off-by: Alban Crequy <alban.crequy@collabora.co.uk>
> ---
>  net/unix/af_unix.c |    3 +++
>  1 files changed, 3 insertions(+), 0 deletions(-)
> 
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 617bea4..bca0c1e 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -1697,6 +1697,9 @@ static int unix_dgram_recvmsg(struct kiocb *iocb, struct socket *sock,
>  	if (err)
>  		goto out_free;
>  
> +	if (sock_flag(sk, SOCK_RCVTSTAMP))
> +		__sock_recv_timestamp(msg, sk, skb);
> +
>  	if (!siocb->scm) {
>  		siocb->scm = &tmp_scm;
>  		memset(&tmp_scm, 0, sizeof(tmp_scm));

Hmm, but how is set skb->tstamp ?

I think its zero at this point, so __sock_recv_timestamp() use current
time, not time of send().

gettimeofday() might be better/cheaper :)

^ permalink raw reply

* [PATCH] AF_UNIX: Implement SO_TIMESTAMP and SO_TIMETAMPNS on Unix sockets
From: Alban Crequy @ 2010-10-04 18:01 UTC (permalink / raw)
  Cc: Lennart Poettering, Alban Crequy, David S. Miller,
	Stephen Hemminger, Eric Dumazet, Cyrill Gorcunov, Alexey Dobriyan,
	linux-kernel, netdev
In-Reply-To: <20101004190049.783f14ab@chocolatine.cbg.collabora.co.uk>

Userspace applications can already request to receive timestamps with:
setsockopt(sockfd, SOL_SOCKET, SO_TIMESTAMP, ...)

Although setsockopt() returns zero (success), timestamps are not added to the
ancillary data. This patch fixes that on SOCK_DGRAM and SOCK_SEQPACKET Unix
sockets.

Signed-off-by: Alban Crequy <alban.crequy@collabora.co.uk>
---
 net/unix/af_unix.c |    3 +++
 1 files changed, 3 insertions(+), 0 deletions(-)

diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 617bea4..bca0c1e 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1697,6 +1697,9 @@ static int unix_dgram_recvmsg(struct kiocb *iocb, struct socket *sock,
 	if (err)
 		goto out_free;
 
+	if (sock_flag(sk, SOCK_RCVTSTAMP))
+		__sock_recv_timestamp(msg, sk, skb);
+
 	if (!siocb->scm) {
 		siocb->scm = &tmp_scm;
 		memset(&tmp_scm, 0, sizeof(tmp_scm));
-- 
1.7.1


^ permalink raw reply related

* Re: ixgbe: normalize frag_list usage
From: Alexander Duyck @ 2010-10-04 18:04 UTC (permalink / raw)
  To: David Miller
  Cc: Kirsher, Jeffrey T, Brandeburg, Jesse, Allan, Bruce W,
	netdev@vger.kernel.org
In-Reply-To: <20101003.235400.229759758.davem@davemloft.net>

David Miller wrote:
> Signed-off-by: David S. Miller <davem@davemloft.net>
> ---
> 
> Basically I want to get the whole tree to consistently use
> the convention that the frag list is built as packets arrive
> by using "skb->prev" of the head skb to track the tail member
> of the frag list.
> 
> This will allow me to do something like:
> 
> struct sk_buff {
>        union {
> 		struct {
> 			struct sk_buff *next;
> 			struct sk_buff *prev;
> 		};
> 		struct {
> 			struct sk_buff *frag_next;
> 			struct sk_buff *frag_tail_tracker;
> 		};
> 	}
>  ...
> }
> 
> And have all frag_list code use these members consistently.
> 
> While doing this patch I noticed that there are some left-over bits in
> the IXGBEVF driver that builds these IXGBE style (before my patch)
> skb->prev RSC chains.
> 
> But nothing other than the RX ring shutdown code processes them, so
> likely if they are created they will leak or cause some other kind of
> problem.
> 
> Please take a look, thanks.
> 
> diff --git a/drivers/net/ixgbe/ixgbe.h b/drivers/net/ixgbe/ixgbe.h
> index 5cebc37..434c9fa 100644
> --- a/drivers/net/ixgbe/ixgbe.h
> +++ b/drivers/net/ixgbe/ixgbe.h
> @@ -181,6 +181,7 @@ struct ixgbe_ring {
>  	struct ixgbe_queue_stats stats;
>  	unsigned long reinit_state;
>  	int numa_node;
> +	struct sk_buff *rsc_skb;	/* RSC packet being built */
>  	u64 rsc_count;			/* stat for coalesced packets */
>  	u64 rsc_flush;			/* stats for flushed packets */
>  	u32 restart_queue;		/* track tx queue restarts */

This will not work for RSC due to the fact that it assumes only one RSC 
context is active per ring and that is not the case.  There can be 
multiple RSC combined flows interlaced on the ring.

It occurs to me that RSC needs the inverse of what you need for frag 
list handling.  You state that you need the next and tail pointers, 
while RSC really needs the prev and head pointers.  One possible 
solution for all this would be to just reverse the logic a bit.

What you are setting up right now is essentially a head based layout 
based on the idea that th head it the component you will have access to. 
   RSC on the other hand is a tail based layout.  If we were to overload 
skb->next so that it pointed at head from tail in ixgbe then we could 
make RSC function, and probably even improve the performance a bit by 
getting rid of the extra RSC transform at the end of creating RSC packets.

Below I have made suggestions based on a tail based approach if we 
overloaded the next pointer on the tail packet to point to the head.

> diff --git a/drivers/net/ixgbe/ixgbe_main.c b/drivers/net/ixgbe/ixgbe_main.c
> index c35e13c..43987a4 100644
> --- a/drivers/net/ixgbe/ixgbe_main.c
> +++ b/drivers/net/ixgbe/ixgbe_main.c
> @@ -1130,36 +1130,6 @@ static inline u32 ixgbe_get_rsc_count(union ixgbe_adv_rx_desc *rx_desc)
>  		IXGBE_RXDADV_RSCCNT_SHIFT;
>  }
>  
> -/**
> - * ixgbe_transform_rsc_queue - change rsc queue into a full packet
> - * @skb: pointer to the last skb in the rsc queue
> - * @count: pointer to number of packets coalesced in this context
> - *
> - * This function changes a queue full of hw rsc buffers into a completed
> - * packet.  It uses the ->prev pointers to find the first packet and then
> - * turns it into the frag list owner.
> - **/
> -static inline struct sk_buff *ixgbe_transform_rsc_queue(struct sk_buff *skb,
> -							u64 *count)
> -{
> -	unsigned int frag_list_size = 0;
> -
> -	while (skb->prev) {
> -		struct sk_buff *prev = skb->prev;
> -		frag_list_size += skb->len;
> -		skb->prev = NULL;
> -		skb = prev;
> -		*count += 1;
> -	}
> -
> -	skb_shinfo(skb)->frag_list = skb->next;
> -	skb->next = NULL;
> -	skb->len += frag_list_size;
> -	skb->data_len += frag_list_size;
> -	skb->truesize += frag_list_size;
> -	return skb;
> -}
> -
>  struct ixgbe_rsc_cb {
>  	dma_addr_t dma;
>  	bool delay_unmap;

So this could probably go since we wouldn't need it for the new approach.

> @@ -1216,10 +1186,13 @@ static bool ixgbe_clean_rx_irq(struct ixgbe_q_vector *q_vector,
>  		prefetch(skb->data);
>  		rx_buffer_info->skb = NULL;
>  
> +		if (!(staterr & IXGBE_RXD_STAT_EOP)) {
> +			rx_ring->rsc_skb = skb;
> +			rx_ring->rsc_count++;
> +		}
>  		if (rx_buffer_info->dma) {
>  			if ((adapter->flags2 & IXGBE_FLAG2_RSC_ENABLED) &&
> -			    (!(staterr & IXGBE_RXD_STAT_EOP)) &&
> -				 (!(skb->prev))) {
> +			    rx_ring->rsc_skb == skb) {
>  				/*
>  				 * When HWRSC is enabled, delay unmapping
>  				 * of the first packet. It carries the

This bit of code is based on the flawed assumption that RSC doesn't have 
other packets interlaced with is not true.

> @@ -1279,9 +1252,12 @@ static bool ixgbe_clean_rx_irq(struct ixgbe_q_vector *q_vector,
>  		}
>  
>  		if (staterr & IXGBE_RXD_STAT_EOP) {
> -			if (skb->prev)
> -				skb = ixgbe_transform_rsc_queue(skb,
> -								&(rx_ring->rsc_count));
> +			if (rx_ring->rsc_skb) {
> +				skb = rx_ring->rsc_skb;
> +				rx_ring->rsc_skb = NULL;
> +
> +				skb->prev = NULL;
> +			}
>  			if (adapter->flags2 & IXGBE_FLAG2_RSC_ENABLED) {
>  				if (IXGBE_RSC_CB(skb)->delay_unmap) {
>  					dma_unmap_single(&pdev->dev,

So this bit of code could be changed to something like:
			if (skb->next) {
				skb = skb->next;
				skb->frag_tail_tracker->next == NULL;
			}

> @@ -1307,8 +1283,22 @@ static bool ixgbe_clean_rx_irq(struct ixgbe_q_vector *q_vector,
>  				next_buffer->skb = skb;
>  				next_buffer->dma = 0;
>  			} else {
> -				skb->next = next_buffer->skb;
> -				skb->next->prev = skb;
> +				struct sk_buff *head = rx_ring->rsc_skb;
> +				struct sk_buff *next = next_buffer->skb;
> +
> +				if (!skb_shinfo(head)->frag_list) {
> +					skb_shinfo(head)->frag_list = next;
> +				} else {
> +					head->prev->next = next;
> +				}
> +
> +				/* ->prev tracks the last skb in the frag_list */
> +				head->prev = next;
> +				rx_ring->rsc_count++;
> +		
> +				head->len += next->len;
> +				head->data_len += next->len;
> +				head->truesize += next->len;
>  			}
>  			rx_ring->non_eop_descs++;
>  			goto next_desc;

This piece is a bit trickier, the len/data_len/truesize addition needs 
to be done on all packets and it looks like it was only done on the 
non-EOP packets which may be an issue.  My guess would be that it would 
probably work if we added something along of the lines of the following 
before the EOP check:
		if (skb->next) {
			skb->next->len += skb->len;
			skb->next->data_len += skb->data_len;
			skb->next->truesize += skb->truesize;
			rx_ring->rsc_count++;
		}

My thoughts for the rest of the code for the non-EOP case would be 
something more along the lines of:
		if (skb->next) {
			next_buffer->skb->next = skb->next;
			skb->next->frag_tail_tracker = next_buffer->skb;
			skb->next = next_buffer->skb;
		}

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


^ permalink raw reply

* Re: 2.6.36-rc6-git2: Reported regressions 2.6.34 -> 2.6.35
From: Rafał Miłecki @ 2010-10-04 18:01 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: Linux Kernel Mailing List, Maciej Rutecki, Florian Mickler,
	Andrew Morton, Linus Torvalds, Kernel Testers List,
	Network Development, Linux ACPI, Linux PM List, Linux SCSI List,
	Linux Wireless List, DRI
In-Reply-To: <OjxVjYbf_EJ.A.ReC.pTPqMB@chimera>

2010/10/3 Rafael J. Wysocki <rjw-KKrjLPT3xs0@public.gmane.org>:
> Bug-Entry       : http://bugzilla.kernel.org/show_bug.cgi?id=19302
> Subject         : PROBLEM: kernel crash on USB-modem (Huawei E1750) hangup.
> Submitter       : O01eg <O01eg-o+MxOtu4lMCHXe+LvDLADg@public.gmane.org>
> Date            : 2010-09-26 19:50 (8 days old)
> Message-ID      : <op.vjnn1up1yohvy1@localhost>
> References      : http://marc.info/?l=linux-kernel&m=128553111709569&w=2

Comment from bugzilla:
> I got this bug at 2.6.34-gentoo-r6 and 2.6.36-rc5. Other kernels were not
> checked.

Doesn't sound like regression.

-- 
Rafał

^ permalink raw reply

* Re: [PATCH] AF_UNIX: Implement SO_TIMESTAMP and SO_TIMETAMPNS on Unix sockets
From: Alban Crequy @ 2010-10-04 18:00 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: Lennart Poettering, David S. Miller, Stephen Hemminger,
	Cyrill Gorcunov, Alexey Dobriyan, linux-kernel, netdev,
	Alban Crequy
In-Reply-To: <1286210504.18293.370.camel@edumazet-laptop>

Le Mon, 04 Oct 2010 18:41:44 +0200,
Eric Dumazet <eric.dumazet@gmail.com> a écrit :

> Le lundi 04 octobre 2010 à 16:38 +0100, Alban Crequy a écrit :
> > Userspace applications can already request to receive timestamps
> > with: setsockopt(sockfd, SOL_SOCKET, SO_TIMESTAMP, ...)
> > 
> > Although setsockopt() returns zero (success), timestamps are not
> > added to the ancillary data. This patch fixes that.
> > 
> > Signed-off-by: Alban Crequy <alban.crequy@collabora.co.uk>
> > ---
> >  net/unix/af_unix.c |    4 ++++
> >  1 files changed, 4 insertions(+), 0 deletions(-)
> > 
> > diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> > index 617bea4..142ccea 100644
> > --- a/net/unix/af_unix.c
> > +++ b/net/unix/af_unix.c
> > @@ -1697,6 +1697,8 @@ static int unix_dgram_recvmsg(struct kiocb
> > *iocb, struct socket *sock, if (err)
> >  		goto out_free;
> >  
> > +	sock_recv_timestamp(msg, sk, skb);
> > +
> >  	if (!siocb->scm) {
> >  		siocb->scm = &tmp_scm;
> >  		memset(&tmp_scm, 0, sizeof(tmp_scm));
> > @@ -1877,6 +1879,8 @@ static int unix_stream_recvmsg(struct kiocb
> > *iocb, struct socket *sock, copied += chunk;
> >  		size -= chunk;
> >  
> > +		sock_recv_timestamp(msg, sk, skb);
> > +
> >  		/* Mark read part of skb as used */
> >  		if (!(flags & MSG_PEEK)) {
> >  			skb_pull(skb, chunk);
> 
> Are you sure its needed for unix_stream case ?

I was about to say it does not cost anything to add it...

> We dont do this for TCP for example, only for datagrams.
> 
> As shown in the past, sock_recv_timestamp() is a bit expensive because
> it takes care of many possible options.

Is it really expensive? It looks like a few flag checks in an inline
function to me.

> It would be better to use in AF_UNIX case (only software timestamps) :
> 
> Solution 1) 
> if (sock_flag(sk, SOCK_RCVTSTAMP))
> 	__sock_recv_timestamp(msg, sk, skb);
> 
> Solution 2) 
> Or something already used elsewhere since 2.6.35 and commit
> 767dd03369ac1 (net: speedup sock_recv_ts_and_drops()) :
> 
> sock_recv_ts_and_drops(msg, sk, skb);
> 
> 
> I would vote for the 1) solution

The patch in the next email implements the 1) solution on SOCK_DGRAM and
SOCK_SEQPACKET (without SOCK_STREAM).

-- 
Alban

^ permalink raw reply

* RE: To GRO or not to GRO...
From: Tantilov, Emil S @ 2010-10-04 17:48 UTC (permalink / raw)
  To: David Miller, sysoleg@yandex.ru
  Cc: richard@sauce.co.nz, netdev@vger.kernel.org
In-Reply-To: <20101003.003129.193711520.davem@davemloft.net>

>-----Original Message-----
>From: netdev-owner@vger.kernel.org [mailto:netdev-owner@vger.kernel.org] On
>Behalf Of David Miller
>Sent: Sunday, October 03, 2010 12:31 AM
>To: sysoleg@yandex.ru
>Cc: richard@sauce.co.nz; netdev@vger.kernel.org
>Subject: Re: To GRO or not to GRO...
>
>From: "\"Oleg A. Arkhangelsky\"" <sysoleg@yandex.ru>
>Date: Sun, 03 Oct 2010 11:18:46 +0400
>
>>
>>
>> 03.10.2010, 03:17, "Richard Scobie" <richard@sauce.co.nz>:
>>
>>> In the README for ixgbe-2.1.4, is says:
>>>
>>> " Disable GRO when routing/bridging
>>>    ---------------------------------
>>>    Due to a known kernel issue, GRO must be turned off when
>>> routing/bridging.
>>>    GRO can be turned off via ethtool."
>>>
>>
>> This information is true for LRO, not GRO.
>
>Right.
>
>> I believe that this appeared when ixgbe was converted from LRO
>> to GRO and all occurrences of LRO was blindly replaced by GRO
>> everywhere.
>
>Someome please submit a patch to fix this, thanks.

The information referenced above is from the README included with the out-of-tree ixgbe driver which we provide for download at e1000.sf.net. 

The in-kernel docs are accurate AFAICT.

Thanks,
Emil


^ permalink raw reply

* Re: [PATCH] tms380tr: fix long delays in initialization
From: David Miller @ 2010-10-04 16:42 UTC (permalink / raw)
  To: mroos; +Cc: netdev
In-Reply-To: <alpine.SOC.1.00.1010041135060.12325@math.ut.ee>

From: Meelis Roos <mroos@linux.ee>
Date: Mon, 4 Oct 2010 11:39:02 +0300 (EEST)

>> > tms380tr driver tries to use udelay (meaning busy loop) for several half 
>> > second delays during hardware initialization. Crazy overly long busy 
>> > wait delays mean no delay at all so driver initialization fails without 
>> > waiting. Fix it by using msleep() for long delays and leave it to 
>> > udelay() for short delays.
>> > 
>> > Signed-off-by: Meelis Roos <mroos@linux.ee>
>> 
>> You can't use msleep() here because this code can be invoked
>> from interrupts and thus cannot sleep.
> 
> I checked these two functions that contain long delays that I changed - 
> tms380tr_bringup_diags() and tms380tr_init_adapter() - to be called only 
> from tms380tr_chipset_init() that is only called from tms380tr_open() so 
> no call paths from interrupts AFAICS. Short delyas from interrupt 
> context are not changed in any way, they still use udelay().

tms380tr_init_adapter() gets called from tms380tr_chk_irq() which is
invoked from the interrupt handler.

^ permalink raw reply

* Re: [PATCH] AF_UNIX: Implement SO_TIMESTAMP and SO_TIMETAMPNS on Unix sockets
From: Eric Dumazet @ 2010-10-04 16:41 UTC (permalink / raw)
  To: Alban Crequy
  Cc: Lennart Poettering, David S. Miller, Stephen Hemminger,
	Cyrill Gorcunov, Alexey Dobriyan, linux-kernel, netdev
In-Reply-To: <1286206706-25733-1-git-send-email-alban.crequy@collabora.co.uk>

Le lundi 04 octobre 2010 à 16:38 +0100, Alban Crequy a écrit :
> Userspace applications can already request to receive timestamps with:
> setsockopt(sockfd, SOL_SOCKET, SO_TIMESTAMP, ...)
> 
> Although setsockopt() returns zero (success), timestamps are not added to the
> ancillary data. This patch fixes that.
> 
> Signed-off-by: Alban Crequy <alban.crequy@collabora.co.uk>
> ---
>  net/unix/af_unix.c |    4 ++++
>  1 files changed, 4 insertions(+), 0 deletions(-)
> 
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 617bea4..142ccea 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -1697,6 +1697,8 @@ static int unix_dgram_recvmsg(struct kiocb *iocb, struct socket *sock,
>  	if (err)
>  		goto out_free;
>  
> +	sock_recv_timestamp(msg, sk, skb);
> +
>  	if (!siocb->scm) {
>  		siocb->scm = &tmp_scm;
>  		memset(&tmp_scm, 0, sizeof(tmp_scm));
> @@ -1877,6 +1879,8 @@ static int unix_stream_recvmsg(struct kiocb *iocb, struct socket *sock,
>  		copied += chunk;
>  		size -= chunk;
>  
> +		sock_recv_timestamp(msg, sk, skb);
> +
>  		/* Mark read part of skb as used */
>  		if (!(flags & MSG_PEEK)) {
>  			skb_pull(skb, chunk);

Are you sure its needed for unix_stream case ?

We dont do this for TCP for example, only for datagrams.

As shown in the past, sock_recv_timestamp() is a bit expensive because
it takes care of many possible options.

It would be better to use in AF_UNIX case (only software timestamps) :

Solution 1) 
if (sock_flag(sk, SOCK_RCVTSTAMP))
	__sock_recv_timestamp(msg, sk, skb);

Solution 2) 
Or something already used elsewhere since 2.6.35 and commit
767dd03369ac1 (net: speedup sock_recv_ts_and_drops()) :

sock_recv_ts_and_drops(msg, sk, skb);


I would vote for the 1) solution



^ permalink raw reply

* RE: Software iWARP (SIW) patches
From: Hefty, Sean @ 2010-10-04 16:30 UTC (permalink / raw)
  To: Bernard Metzler, netdev@vger.kernel.org, Nicholas A. Bellinger,
	Steve Wise, Rola
  Cc: Andi Kleen, David Miller, linux-rdma@vger.kernel.org,
	linux-rdma-owner@vger.kernel.org, Matthew Wilcox
In-Reply-To: <OFB52DA6A6.F41C82CB-ONC12577B2.004605E9-C12577B2.004F74B9@ch.ibm.com>

> To avoid redundancy, the patches will be posted to the netdev list only.

Please copy Linux-rdma on the patches.

^ permalink raw reply

* Re: powerpc, fs_enet: scanning PHY after Linux is up
From: Grant Likely @ 2010-10-04 16:20 UTC (permalink / raw)
  To: hs; +Cc: linuxppc-dev, netdev, devicetree-discuss, Holger Brunck,
	Detlev Zundel
In-Reply-To: <4CA9830A.2030605@denx.de>

On Mon, Oct 4, 2010 at 1:32 AM, Heiko Schocher <hs@denx.de> wrote:
> Hello all,
>
> we have on the mgcoge arch/powerpc/boot/dts/mgcoge.dts 3 fs_enet
> devices. The first is accessible on boot, and so get correct
> probed and works fine. For the other two fs_enet devices the PHYs
> are on startup in reset, and gets later, through userapplikations,
> out of reset ... so, on bootup, this 2 fs_enet devices could
> not detect the PHY in drivers/of/of_mdio.c of_mdiobus_register(),
> and if we want to use them later, we get for example:
>
> -bash-3.2# ifconfig eth2 172.31.31.33
> net eth2: Could not attach to PHY
> SIOCSIFFLAGS: No such device
>
> So the problem is, that we cannot rescan the PHYs, if they are
> accessible. Also we could not load the fs_enet driver as a module,
> because the first port is used fix.
>
> So, first question which comes in my mind, is:
>
> Is detecting the phy in drivers/of/of_mdio.c of_mdiobus_register()
> the right place, or should it not better be done, when really
> using the port?
>
> But we found another way to solve this issue:
>
> After the PHYs are out of reset, we just have to rescan the PHYs
> with (for example PHY with addr 1)
>
> err = mdiobus_scan(bus, 1);
>
> and
>
> of_find_node_by_path("/soc@f0000000/cpm@119c0/mdio@10d40/ethernet-phy@1");
> of_node_get(np);
> dev_archdata_set_node(&err->dev.archdata, np);
>
> but thats just a hack ...

Yeah, that's a hack.  It really needs to be done via the of_mdio
mechanisms so that dt <--> phy_device linkages remain consistent.

> So, the question is, is there a possibility to solve this problem?
>
> If there is no standard option, what would be with adding a
> "scan_phy" file in
>
> /proc/device-tree/soc\@f0000000/cpm\@119c0/mdio\@10d40
> (or better destination?)
>
> which with we could rescan a PHY with
> "echo addr > /proc/device-tree/soc\@f0000000/cpm\@119c0/mdio\@10d40/scan_phy"
> (so there is no need for using of_find_node_by_path(), as we should
>  have the associated device node here, and can step through the child
>  nodes with "for_each_child_of_node(np, child)" and check if reg == addr)
>
> or shouldn;t be at least, if the phy couldn;t be found when opening
> the port, retrigger a scanning, if the phy now is accessible?

One option would be to still register a phy_device for each phy
described in the device tree, but defer binding a driver to each phy
that doesn't respond.  Then at of_phy_find_device() time, if it
matches with a phy_device that isn't bound to a driver yet, then
re-trigger the binding operation.  At which point the phy id can be
probed and the correct driver can be chosen.  If binding succeeds,
then return the phy_device handle.  If not, then fail as it currently
does.

g.

^ permalink raw reply

* [PATCH net-next] net neigh: RCU conversion of neigh hash table
From: Eric Dumazet @ 2010-10-04 16:15 UTC (permalink / raw)
  To: David Miller; +Cc: netdev

David

This is the first step for RCU conversion of neigh code.

Next patches will convert hash_buckets[] and "struct neighbour" to RCU
protected objects.

Thanks

[PATCH net-next] net neigh: RCU conversion of neigh hash table

Instead of storing hash_buckets, hash_mask and hash_rnd in "struct
neigh_table", a new structure is defined :

struct neigh_hash_table {
       struct neighbour        **hash_buckets;
       unsigned int            hash_mask;
       __u32                   hash_rnd;
       struct rcu_head         rcu;
};

And "struct neigh_table" has an RCU protected pointer to such a
neigh_hash_table.

This means the signature of (*hash)() function changed: We need to add a
third parameter with the actual hash_rnd value, since this is not
anymore a neigh_table field.

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
---
 include/net/neighbour.h |   16 ++
 net/atm/clip.c          |    4 
 net/core/neighbour.c    |  219 +++++++++++++++++++++++---------------
 net/decnet/dn_neigh.c   |   13 +-
 net/ipv4/arp.c          |    8 -
 net/ipv6/ndisc.c        |   10 +
 6 files changed, 170 insertions(+), 100 deletions(-)

diff --git a/include/net/neighbour.h b/include/net/neighbour.h
index 7d08fd1..37845da 100644
--- a/include/net/neighbour.h
+++ b/include/net/neighbour.h
@@ -138,13 +138,22 @@ struct pneigh_entry {
  *	neighbour table manipulation
  */
 
+struct neigh_hash_table {
+	struct neighbour	**hash_buckets;
+	unsigned int		hash_mask;
+	__u32			hash_rnd;
+	struct rcu_head		rcu;
+};
+
 
 struct neigh_table {
 	struct neigh_table	*next;
 	int			family;
 	int			entry_size;
 	int			key_len;
-	__u32			(*hash)(const void *pkey, const struct net_device *);
+	__u32			(*hash)(const void *pkey,
+					const struct net_device *dev,
+					__u32 hash_rnd);
 	int			(*constructor)(struct neighbour *);
 	int			(*pconstructor)(struct pneigh_entry *);
 	void			(*pdestructor)(struct pneigh_entry *);
@@ -165,9 +174,7 @@ struct neigh_table {
 	unsigned long		last_rand;
 	struct kmem_cache	*kmem_cachep;
 	struct neigh_statistics	__percpu *stats;
-	struct neighbour	**hash_buckets;
-	unsigned int		hash_mask;
-	__u32			hash_rnd;
+	struct neigh_hash_table __rcu *nht;
 	struct pneigh_entry	**phash_buckets;
 };
 
@@ -237,6 +244,7 @@ extern void pneigh_for_each(struct neigh_table *tbl, void (*cb)(struct pneigh_en
 struct neigh_seq_state {
 	struct seq_net_private p;
 	struct neigh_table *tbl;
+	struct neigh_hash_table *nht;
 	void *(*neigh_sub_iter)(struct neigh_seq_state *state,
 				struct neighbour *n, loff_t *pos);
 	unsigned int bucket;
diff --git a/net/atm/clip.c b/net/atm/clip.c
index 95fdd11..ff956d1 100644
--- a/net/atm/clip.c
+++ b/net/atm/clip.c
@@ -310,9 +310,9 @@ static int clip_constructor(struct neighbour *neigh)
 	return 0;
 }
 
-static u32 clip_hash(const void *pkey, const struct net_device *dev)
+static u32 clip_hash(const void *pkey, const struct net_device *dev, __u32 rnd)
 {
-	return jhash_2words(*(u32 *) pkey, dev->ifindex, clip_tbl.hash_rnd);
+	return jhash_2words(*(u32 *) pkey, dev->ifindex, rnd);
 }
 
 static struct neigh_table clip_tbl = {
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index d6996e0..dd8920e 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c
@@ -131,14 +131,17 @@ static int neigh_forced_gc(struct neigh_table *tbl)
 {
 	int shrunk = 0;
 	int i;
+	struct neigh_hash_table *nht;
 
 	NEIGH_CACHE_STAT_INC(tbl, forced_gc_runs);
 
 	write_lock_bh(&tbl->lock);
-	for (i = 0; i <= tbl->hash_mask; i++) {
+	nht = rcu_dereference_protected(tbl->nht,
+					lockdep_is_held(&tbl->lock));
+	for (i = 0; i <= nht->hash_mask; i++) {
 		struct neighbour *n, **np;
 
-		np = &tbl->hash_buckets[i];
+		np = &nht->hash_buckets[i];
 		while ((n = *np) != NULL) {
 			/* Neighbour record may be discarded if:
 			 * - nobody refers to it.
@@ -199,9 +202,13 @@ static void pneigh_queue_purge(struct sk_buff_head *list)
 static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev)
 {
 	int i;
+	struct neigh_hash_table *nht;
 
-	for (i = 0; i <= tbl->hash_mask; i++) {
-		struct neighbour *n, **np = &tbl->hash_buckets[i];
+	nht = rcu_dereference_protected(tbl->nht,
+					lockdep_is_held(&tbl->lock));
+
+	for (i = 0; i <= nht->hash_mask; i++) {
+		struct neighbour *n, **np = &nht->hash_buckets[i];
 
 		while ((n = *np) != NULL) {
 			if (dev && n->dev != dev) {
@@ -297,64 +304,81 @@ out_entries:
 	goto out;
 }
 
-static struct neighbour **neigh_hash_alloc(unsigned int entries)
+static struct neigh_hash_table *neigh_hash_alloc(unsigned int entries)
 {
-	unsigned long size = entries * sizeof(struct neighbour *);
-	struct neighbour **ret;
+	size_t size = entries * sizeof(struct neighbour *);
+	struct neigh_hash_table *ret;
+	struct neighbour **buckets;
 
-	if (size <= PAGE_SIZE) {
-		ret = kzalloc(size, GFP_ATOMIC);
-	} else {
-		ret = (struct neighbour **)
-		      __get_free_pages(GFP_ATOMIC|__GFP_ZERO, get_order(size));
+	ret = kmalloc(sizeof(*ret), GFP_ATOMIC);
+	if (!ret)
+		return NULL;
+	if (size <= PAGE_SIZE)
+		buckets = kzalloc(size, GFP_ATOMIC);
+	else
+		buckets = (struct neighbour **)
+			  __get_free_pages(GFP_ATOMIC | __GFP_ZERO,
+					   get_order(size));
+	if (!buckets) {
+		kfree(ret);
+		return NULL;
 	}
+	ret->hash_buckets = buckets;
+	ret->hash_mask = entries - 1;
+	get_random_bytes(&ret->hash_rnd, sizeof(ret->hash_rnd));
 	return ret;
 }
 
-static void neigh_hash_free(struct neighbour **hash, unsigned int entries)
+static void neigh_hash_free_rcu(struct rcu_head *head)
 {
-	unsigned long size = entries * sizeof(struct neighbour *);
+	struct neigh_hash_table *nht = container_of(head,
+						    struct neigh_hash_table,
+						    rcu);
+	size_t size = (nht->hash_mask + 1) * sizeof(struct neighbour *);
+	struct neighbour **buckets = nht->hash_buckets;
 
 	if (size <= PAGE_SIZE)
-		kfree(hash);
+		kfree(buckets);
 	else
-		free_pages((unsigned long)hash, get_order(size));
+		free_pages((unsigned long)buckets, get_order(size));
+	kfree(nht);
 }
 
-static void neigh_hash_grow(struct neigh_table *tbl, unsigned long new_entries)
+static struct neigh_hash_table *neigh_hash_grow(struct neigh_table *tbl,
+						unsigned long new_entries)
 {
-	struct neighbour **new_hash, **old_hash;
-	unsigned int i, new_hash_mask, old_entries;
+	unsigned int i, hash;
+	struct neigh_hash_table *new_nht, *old_nht;
 
 	NEIGH_CACHE_STAT_INC(tbl, hash_grows);
 
 	BUG_ON(!is_power_of_2(new_entries));
-	new_hash = neigh_hash_alloc(new_entries);
-	if (!new_hash)
-		return;
+	old_nht = rcu_dereference_protected(tbl->nht,
+					    lockdep_is_held(&tbl->lock));
+	new_nht = neigh_hash_alloc(new_entries);
+	if (!new_nht)
+		return old_nht;
 
-	old_entries = tbl->hash_mask + 1;
-	new_hash_mask = new_entries - 1;
-	old_hash = tbl->hash_buckets;
-
-	get_random_bytes(&tbl->hash_rnd, sizeof(tbl->hash_rnd));
-	for (i = 0; i < old_entries; i++) {
+	for (i = 0; i <= old_nht->hash_mask; i++) {
 		struct neighbour *n, *next;
 
-		for (n = old_hash[i]; n; n = next) {
-			unsigned int hash_val = tbl->hash(n->primary_key, n->dev);
+		for (n = old_nht->hash_buckets[i];
+		     n != NULL;
+		     n = next) {
+			hash = tbl->hash(n->primary_key, n->dev,
+					 new_nht->hash_rnd);
 
-			hash_val &= new_hash_mask;
+			hash &= new_nht->hash_mask;
 			next = n->next;
 
-			n->next = new_hash[hash_val];
-			new_hash[hash_val] = n;
+			n->next = new_nht->hash_buckets[hash];
+			new_nht->hash_buckets[hash] = n;
 		}
 	}
-	tbl->hash_buckets = new_hash;
-	tbl->hash_mask = new_hash_mask;
 
-	neigh_hash_free(old_hash, old_entries);
+	rcu_assign_pointer(tbl->nht, new_nht);
+	call_rcu(&old_nht->rcu, neigh_hash_free_rcu);
+	return new_nht;
 }
 
 struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
@@ -363,19 +387,23 @@ struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
 	struct neighbour *n;
 	int key_len = tbl->key_len;
 	u32 hash_val;
+	struct neigh_hash_table *nht;
 
 	NEIGH_CACHE_STAT_INC(tbl, lookups);
 
-	read_lock_bh(&tbl->lock);
-	hash_val = tbl->hash(pkey, dev);
-	for (n = tbl->hash_buckets[hash_val & tbl->hash_mask]; n; n = n->next) {
+	rcu_read_lock_bh();
+	nht = rcu_dereference_bh(tbl->nht);
+	hash_val = tbl->hash(pkey, dev, nht->hash_rnd) & nht->hash_mask;
+	read_lock(&tbl->lock);
+	for (n = nht->hash_buckets[hash_val]; n; n = n->next) {
 		if (dev == n->dev && !memcmp(n->primary_key, pkey, key_len)) {
 			neigh_hold(n);
 			NEIGH_CACHE_STAT_INC(tbl, hits);
 			break;
 		}
 	}
-	read_unlock_bh(&tbl->lock);
+	read_unlock(&tbl->lock);
+	rcu_read_unlock_bh();
 	return n;
 }
 EXPORT_SYMBOL(neigh_lookup);
@@ -386,12 +414,15 @@ struct neighbour *neigh_lookup_nodev(struct neigh_table *tbl, struct net *net,
 	struct neighbour *n;
 	int key_len = tbl->key_len;
 	u32 hash_val;
+	struct neigh_hash_table *nht;
 
 	NEIGH_CACHE_STAT_INC(tbl, lookups);
 
-	read_lock_bh(&tbl->lock);
-	hash_val = tbl->hash(pkey, NULL);
-	for (n = tbl->hash_buckets[hash_val & tbl->hash_mask]; n; n = n->next) {
+	rcu_read_lock_bh();
+	nht = rcu_dereference_bh(tbl->nht);
+	hash_val = tbl->hash(pkey, NULL, nht->hash_rnd) & nht->hash_mask;
+	read_lock(&tbl->lock);
+	for (n = nht->hash_buckets[hash_val]; n; n = n->next) {
 		if (!memcmp(n->primary_key, pkey, key_len) &&
 		    net_eq(dev_net(n->dev), net)) {
 			neigh_hold(n);
@@ -399,7 +430,8 @@ struct neighbour *neigh_lookup_nodev(struct neigh_table *tbl, struct net *net,
 			break;
 		}
 	}
-	read_unlock_bh(&tbl->lock);
+	read_unlock(&tbl->lock);
+	rcu_read_unlock_bh();
 	return n;
 }
 EXPORT_SYMBOL(neigh_lookup_nodev);
@@ -411,6 +443,7 @@ struct neighbour *neigh_create(struct neigh_table *tbl, const void *pkey,
 	int key_len = tbl->key_len;
 	int error;
 	struct neighbour *n1, *rc, *n = neigh_alloc(tbl);
+	struct neigh_hash_table *nht;
 
 	if (!n) {
 		rc = ERR_PTR(-ENOBUFS);
@@ -437,18 +470,20 @@ struct neighbour *neigh_create(struct neigh_table *tbl, const void *pkey,
 	n->confirmed = jiffies - (n->parms->base_reachable_time << 1);
 
 	write_lock_bh(&tbl->lock);
+	nht = rcu_dereference_protected(tbl->nht,
+					lockdep_is_held(&tbl->lock));
 
-	if (atomic_read(&tbl->entries) > (tbl->hash_mask + 1))
-		neigh_hash_grow(tbl, (tbl->hash_mask + 1) << 1);
+	if (atomic_read(&tbl->entries) > (nht->hash_mask + 1))
+		nht = neigh_hash_grow(tbl, (nht->hash_mask + 1) << 1);
 
-	hash_val = tbl->hash(pkey, dev) & tbl->hash_mask;
+	hash_val = tbl->hash(pkey, dev, nht->hash_rnd) & nht->hash_mask;
 
 	if (n->parms->dead) {
 		rc = ERR_PTR(-EINVAL);
 		goto out_tbl_unlock;
 	}
 
-	for (n1 = tbl->hash_buckets[hash_val]; n1; n1 = n1->next) {
+	for (n1 = nht->hash_buckets[hash_val]; n1; n1 = n1->next) {
 		if (dev == n1->dev && !memcmp(n1->primary_key, pkey, key_len)) {
 			neigh_hold(n1);
 			rc = n1;
@@ -456,8 +491,8 @@ struct neighbour *neigh_create(struct neigh_table *tbl, const void *pkey,
 		}
 	}
 
-	n->next = tbl->hash_buckets[hash_val];
-	tbl->hash_buckets[hash_val] = n;
+	n->next = nht->hash_buckets[hash_val];
+	nht->hash_buckets[hash_val] = n;
 	n->dead = 0;
 	neigh_hold(n);
 	write_unlock_bh(&tbl->lock);
@@ -698,10 +733,13 @@ static void neigh_periodic_work(struct work_struct *work)
 	struct neigh_table *tbl = container_of(work, struct neigh_table, gc_work.work);
 	struct neighbour *n, **np;
 	unsigned int i;
+	struct neigh_hash_table *nht;
 
 	NEIGH_CACHE_STAT_INC(tbl, periodic_gc_runs);
 
 	write_lock_bh(&tbl->lock);
+	nht = rcu_dereference_protected(tbl->nht,
+					lockdep_is_held(&tbl->lock));
 
 	/*
 	 *	periodically recompute ReachableTime from random function
@@ -715,8 +753,8 @@ static void neigh_periodic_work(struct work_struct *work)
 				neigh_rand_reach_time(p->base_reachable_time);
 	}
 
-	for (i = 0 ; i <= tbl->hash_mask; i++) {
-		np = &tbl->hash_buckets[i];
+	for (i = 0 ; i <= nht->hash_mask; i++) {
+		np = &nht->hash_buckets[i];
 
 		while ((n = *np) != NULL) {
 			unsigned int state;
@@ -1438,17 +1476,14 @@ void neigh_table_init_no_netlink(struct neigh_table *tbl)
 		panic("cannot create neighbour proc dir entry");
 #endif
 
-	tbl->hash_mask = 1;
-	tbl->hash_buckets = neigh_hash_alloc(tbl->hash_mask + 1);
+	tbl->nht = neigh_hash_alloc(8);
 
 	phsize = (PNEIGH_HASHMASK + 1) * sizeof(struct pneigh_entry *);
 	tbl->phash_buckets = kzalloc(phsize, GFP_KERNEL);
 
-	if (!tbl->hash_buckets || !tbl->phash_buckets)
+	if (!tbl->nht || !tbl->phash_buckets)
 		panic("cannot allocate neighbour cache hashes");
 
-	get_random_bytes(&tbl->hash_rnd, sizeof(tbl->hash_rnd));
-
 	rwlock_init(&tbl->lock);
 	INIT_DELAYED_WORK_DEFERRABLE(&tbl->gc_work, neigh_periodic_work);
 	schedule_delayed_work(&tbl->gc_work, tbl->parms.reachable_time);
@@ -1504,8 +1539,8 @@ int neigh_table_clear(struct neigh_table *tbl)
 	}
 	write_unlock(&neigh_tbl_lock);
 
-	neigh_hash_free(tbl->hash_buckets, tbl->hash_mask + 1);
-	tbl->hash_buckets = NULL;
+	call_rcu(&tbl->nht->rcu, neigh_hash_free_rcu);
+	tbl->nht = NULL;
 
 	kfree(tbl->phash_buckets);
 	tbl->phash_buckets = NULL;
@@ -1745,18 +1780,22 @@ static int neightbl_fill_info(struct sk_buff *skb, struct neigh_table *tbl,
 		unsigned long now = jiffies;
 		unsigned int flush_delta = now - tbl->last_flush;
 		unsigned int rand_delta = now - tbl->last_rand;
-
+		struct neigh_hash_table *nht;
 		struct ndt_config ndc = {
 			.ndtc_key_len		= tbl->key_len,
 			.ndtc_entry_size	= tbl->entry_size,
 			.ndtc_entries		= atomic_read(&tbl->entries),
 			.ndtc_last_flush	= jiffies_to_msecs(flush_delta),
 			.ndtc_last_rand		= jiffies_to_msecs(rand_delta),
-			.ndtc_hash_rnd		= tbl->hash_rnd,
-			.ndtc_hash_mask		= tbl->hash_mask,
 			.ndtc_proxy_qlen	= tbl->proxy_queue.qlen,
 		};
 
+		rcu_read_lock_bh();
+		nht = rcu_dereference_bh(tbl->nht);
+		ndc.ndtc_hash_rnd = nht->hash_rnd;
+		ndc.ndtc_hash_mask = nht->hash_mask;
+		rcu_read_unlock_bh();
+
 		NLA_PUT(skb, NDTA_CONFIG, sizeof(ndc), &ndc);
 	}
 
@@ -2088,14 +2127,18 @@ static int neigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,
 	struct neighbour *n;
 	int rc, h, s_h = cb->args[1];
 	int idx, s_idx = idx = cb->args[2];
+	struct neigh_hash_table *nht;
 
-	read_lock_bh(&tbl->lock);
-	for (h = 0; h <= tbl->hash_mask; h++) {
+	rcu_read_lock_bh();
+	nht = rcu_dereference_bh(tbl->nht);
+
+	read_lock(&tbl->lock);
+	for (h = 0; h <= nht->hash_mask; h++) {
 		if (h < s_h)
 			continue;
 		if (h > s_h)
 			s_idx = 0;
-		for (n = tbl->hash_buckets[h], idx = 0; n; n = n->next) {
+		for (n = nht->hash_buckets[h], idx = 0; n; n = n->next) {
 			if (!net_eq(dev_net(n->dev), net))
 				continue;
 			if (idx < s_idx)
@@ -2104,7 +2147,6 @@ static int neigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,
 					    cb->nlh->nlmsg_seq,
 					    RTM_NEWNEIGH,
 					    NLM_F_MULTI) <= 0) {
-				read_unlock_bh(&tbl->lock);
 				rc = -1;
 				goto out;
 			}
@@ -2112,9 +2154,10 @@ static int neigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,
 			idx++;
 		}
 	}
-	read_unlock_bh(&tbl->lock);
 	rc = skb->len;
 out:
+	read_unlock(&tbl->lock);
+	rcu_read_unlock_bh();
 	cb->args[1] = h;
 	cb->args[2] = idx;
 	return rc;
@@ -2147,15 +2190,20 @@ static int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
 void neigh_for_each(struct neigh_table *tbl, void (*cb)(struct neighbour *, void *), void *cookie)
 {
 	int chain;
+	struct neigh_hash_table *nht;
 
-	read_lock_bh(&tbl->lock);
-	for (chain = 0; chain <= tbl->hash_mask; chain++) {
+	rcu_read_lock_bh();
+	nht = rcu_dereference_bh(tbl->nht);
+
+	read_lock(&tbl->lock);
+	for (chain = 0; chain <= nht->hash_mask; chain++) {
 		struct neighbour *n;
 
-		for (n = tbl->hash_buckets[chain]; n; n = n->next)
+		for (n = nht->hash_buckets[chain]; n; n = n->next)
 			cb(n, cookie);
 	}
-	read_unlock_bh(&tbl->lock);
+	read_unlock(&tbl->lock);
+	rcu_read_unlock_bh();
 }
 EXPORT_SYMBOL(neigh_for_each);
 
@@ -2164,11 +2212,14 @@ void __neigh_for_each_release(struct neigh_table *tbl,
 			      int (*cb)(struct neighbour *))
 {
 	int chain;
+	struct neigh_hash_table *nht;
 
-	for (chain = 0; chain <= tbl->hash_mask; chain++) {
+	nht = rcu_dereference_protected(tbl->nht,
+					lockdep_is_held(&tbl->lock));
+	for (chain = 0; chain <= nht->hash_mask; chain++) {
 		struct neighbour *n, **np;
 
-		np = &tbl->hash_buckets[chain];
+		np = &nht->hash_buckets[chain];
 		while ((n = *np) != NULL) {
 			int release;
 
@@ -2193,13 +2244,13 @@ static struct neighbour *neigh_get_first(struct seq_file *seq)
 {
 	struct neigh_seq_state *state = seq->private;
 	struct net *net = seq_file_net(seq);
-	struct neigh_table *tbl = state->tbl;
+	struct neigh_hash_table *nht = state->nht;
 	struct neighbour *n = NULL;
 	int bucket = state->bucket;
 
 	state->flags &= ~NEIGH_SEQ_IS_PNEIGH;
-	for (bucket = 0; bucket <= tbl->hash_mask; bucket++) {
-		n = tbl->hash_buckets[bucket];
+	for (bucket = 0; bucket <= nht->hash_mask; bucket++) {
+		n = nht->hash_buckets[bucket];
 
 		while (n) {
 			if (!net_eq(dev_net(n->dev), net))
@@ -2234,7 +2285,7 @@ static struct neighbour *neigh_get_next(struct seq_file *seq,
 {
 	struct neigh_seq_state *state = seq->private;
 	struct net *net = seq_file_net(seq);
-	struct neigh_table *tbl = state->tbl;
+	struct neigh_hash_table *nht = state->nht;
 
 	if (state->neigh_sub_iter) {
 		void *v = state->neigh_sub_iter(state, n, pos);
@@ -2265,10 +2316,10 @@ static struct neighbour *neigh_get_next(struct seq_file *seq,
 		if (n)
 			break;
 
-		if (++state->bucket > tbl->hash_mask)
+		if (++state->bucket > nht->hash_mask)
 			break;
 
-		n = tbl->hash_buckets[state->bucket];
+		n = nht->hash_buckets[state->bucket];
 	}
 
 	if (n && pos)
@@ -2367,6 +2418,7 @@ static void *neigh_get_idx_any(struct seq_file *seq, loff_t *pos)
 
 void *neigh_seq_start(struct seq_file *seq, loff_t *pos, struct neigh_table *tbl, unsigned int neigh_seq_flags)
 	__acquires(tbl->lock)
+	__acquires(rcu_bh)
 {
 	struct neigh_seq_state *state = seq->private;
 
@@ -2374,8 +2426,9 @@ void *neigh_seq_start(struct seq_file *seq, loff_t *pos, struct neigh_table *tbl
 	state->bucket = 0;
 	state->flags = (neigh_seq_flags & ~NEIGH_SEQ_IS_PNEIGH);
 
-	read_lock_bh(&tbl->lock);
-
+	rcu_read_lock_bh();
+	state->nht = rcu_dereference_bh(tbl->nht);
+	read_lock(&tbl->lock);
 	return *pos ? neigh_get_idx_any(seq, pos) : SEQ_START_TOKEN;
 }
 EXPORT_SYMBOL(neigh_seq_start);
@@ -2409,11 +2462,13 @@ EXPORT_SYMBOL(neigh_seq_next);
 
 void neigh_seq_stop(struct seq_file *seq, void *v)
 	__releases(tbl->lock)
+	__releases(rcu_bh)
 {
 	struct neigh_seq_state *state = seq->private;
 	struct neigh_table *tbl = state->tbl;
 
-	read_unlock_bh(&tbl->lock);
+	read_unlock(&tbl->lock);
+	rcu_read_unlock_bh();
 }
 EXPORT_SYMBOL(neigh_seq_stop);
 
diff --git a/net/decnet/dn_neigh.c b/net/decnet/dn_neigh.c
index 0363bb9..a085dbc 100644
--- a/net/decnet/dn_neigh.c
+++ b/net/decnet/dn_neigh.c
@@ -48,7 +48,6 @@
 #include <net/dn_neigh.h>
 #include <net/dn_route.h>
 
-static u32 dn_neigh_hash(const void *pkey, const struct net_device *dev);
 static int dn_neigh_construct(struct neighbour *);
 static void dn_long_error_report(struct neighbour *, struct sk_buff *);
 static void dn_short_error_report(struct neighbour *, struct sk_buff *);
@@ -93,6 +92,13 @@ static const struct neigh_ops dn_phase3_ops = {
 	.queue_xmit =		dev_queue_xmit
 };
 
+static u32 dn_neigh_hash(const void *pkey,
+			 const struct net_device *dev,
+			 __u32 hash_rnd)
+{
+	return jhash_2words(*(__u16 *)pkey, 0, hash_rnd);
+}
+
 struct neigh_table dn_neigh_table = {
 	.family =			PF_DECnet,
 	.entry_size =			sizeof(struct dn_neigh),
@@ -122,11 +128,6 @@ struct neigh_table dn_neigh_table = {
 	.gc_thresh3 =			1024,
 };
 
-static u32 dn_neigh_hash(const void *pkey, const struct net_device *dev)
-{
-	return jhash_2words(*(__u16 *)pkey, 0, dn_neigh_table.hash_rnd);
-}
-
 static int dn_neigh_construct(struct neighbour *neigh)
 {
 	struct net_device *dev = neigh->dev;
diff --git a/net/ipv4/arp.c b/net/ipv4/arp.c
index d9031ad..f353095 100644
--- a/net/ipv4/arp.c
+++ b/net/ipv4/arp.c
@@ -127,7 +127,7 @@ EXPORT_SYMBOL(clip_tbl_hook);
 /*
  *	Interface to generic neighbour cache.
  */
-static u32 arp_hash(const void *pkey, const struct net_device *dev);
+static u32 arp_hash(const void *pkey, const struct net_device *dev, __u32 rnd);
 static int arp_constructor(struct neighbour *neigh);
 static void arp_solicit(struct neighbour *neigh, struct sk_buff *skb);
 static void arp_error_report(struct neighbour *neigh, struct sk_buff *skb);
@@ -225,9 +225,11 @@ int arp_mc_map(__be32 addr, u8 *haddr, struct net_device *dev, int dir)
 }
 
 
-static u32 arp_hash(const void *pkey, const struct net_device *dev)
+static u32 arp_hash(const void *pkey,
+		    const struct net_device *dev,
+		    __u32 hash_rnd)
 {
-	return jhash_2words(*(u32 *)pkey, dev->ifindex, arp_tbl.hash_rnd);
+	return jhash_2words(*(u32 *)pkey, dev->ifindex, hash_rnd);
 }
 
 static int arp_constructor(struct neighbour *neigh)
diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index b3dd844..998d6d2 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -91,7 +91,9 @@
 #include <linux/netfilter.h>
 #include <linux/netfilter_ipv6.h>
 
-static u32 ndisc_hash(const void *pkey, const struct net_device *dev);
+static u32 ndisc_hash(const void *pkey,
+		      const struct net_device *dev,
+		      __u32 rnd);
 static int ndisc_constructor(struct neighbour *neigh);
 static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb);
 static void ndisc_error_report(struct neighbour *neigh, struct sk_buff *skb);
@@ -350,7 +352,9 @@ int ndisc_mc_map(struct in6_addr *addr, char *buf, struct net_device *dev, int d
 
 EXPORT_SYMBOL(ndisc_mc_map);
 
-static u32 ndisc_hash(const void *pkey, const struct net_device *dev)
+static u32 ndisc_hash(const void *pkey,
+		      const struct net_device *dev,
+		      __u32 hash_rnd)
 {
 	const u32 *p32 = pkey;
 	u32 addr_hash, i;
@@ -359,7 +363,7 @@ static u32 ndisc_hash(const void *pkey, const struct net_device *dev)
 	for (i = 0; i < (sizeof(struct in6_addr) / sizeof(u32)); i++)
 		addr_hash ^= *p32++;
 
-	return jhash_2words(addr_hash, dev->ifindex, nd_tbl.hash_rnd);
+	return jhash_2words(addr_hash, dev->ifindex, hash_rnd);
 }
 
 static int ndisc_constructor(struct neighbour *neigh)



^ permalink raw reply related

* [PATCH] AF_UNIX: Implement SO_TIMESTAMP and SO_TIMETAMPNS on Unix sockets
From: Alban Crequy @ 2010-10-04 15:38 UTC (permalink / raw)
  Cc: Lennart Poettering, Alban Crequy, David S. Miller,
	Stephen Hemminger, Eric Dumazet, Cyrill Gorcunov, Alexey Dobriyan,
	linux-kernel, netdev

Userspace applications can already request to receive timestamps with:
setsockopt(sockfd, SOL_SOCKET, SO_TIMESTAMP, ...)

Although setsockopt() returns zero (success), timestamps are not added to the
ancillary data. This patch fixes that.

Signed-off-by: Alban Crequy <alban.crequy@collabora.co.uk>
---
 net/unix/af_unix.c |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 617bea4..142ccea 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1697,6 +1697,8 @@ static int unix_dgram_recvmsg(struct kiocb *iocb, struct socket *sock,
 	if (err)
 		goto out_free;
 
+	sock_recv_timestamp(msg, sk, skb);
+
 	if (!siocb->scm) {
 		siocb->scm = &tmp_scm;
 		memset(&tmp_scm, 0, sizeof(tmp_scm));
@@ -1877,6 +1879,8 @@ static int unix_stream_recvmsg(struct kiocb *iocb, struct socket *sock,
 		copied += chunk;
 		size -= chunk;
 
+		sock_recv_timestamp(msg, sk, skb);
+
 		/* Mark read part of skb as used */
 		if (!(flags & MSG_PEEK)) {
 			skb_pull(skb, chunk);
-- 
1.7.1

^ permalink raw reply related

* RE: ixgbe: normalize frag_list usage
From: Rose, Gregory V @ 2010-10-04 15:37 UTC (permalink / raw)
  To: David Miller, Kirsher, Jeffrey T
  Cc: Brandeburg, Jesse, Allan, Bruce W, netdev@vger.kernel.org
In-Reply-To: <20101003.235400.229759758.davem@davemloft.net>

RSC isn't actually supported in virtual functions or even when SR-IOV mode is enabled.  Any left over bits are just cruft from when the vf driver was ported from the pf driver.

I'll have a look at it.

- Gerg

> -----Original Message-----
> From: netdev-owner@vger.kernel.org [mailto:netdev-owner@vger.kernel.org] On
> Behalf Of David Miller
> Sent: Sunday, October 03, 2010 11:54 PM
> To: Kirsher, Jeffrey T
> Cc: Brandeburg, Jesse; Allan, Bruce W; netdev@vger.kernel.org
> Subject: ixgbe: normalize frag_list usage
> 
> 
> Signed-off-by: David S. Miller <davem@davemloft.net>
> ---
> 
> Basically I want to get the whole tree to consistently use
> the convention that the frag list is built as packets arrive
> by using "skb->prev" of the head skb to track the tail member
> of the frag list.
> 
> This will allow me to do something like:
> 
> struct sk_buff {
>        union {
> 		struct {
> 			struct sk_buff *next;
> 			struct sk_buff *prev;
> 		};
> 		struct {
> 			struct sk_buff *frag_next;
> 			struct sk_buff *frag_tail_tracker;
> 		};
> 	}
>  ...
> }
> 
> And have all frag_list code use these members consistently.
> 
> While doing this patch I noticed that there are some left-over bits in
> the IXGBEVF driver that builds these IXGBE style (before my patch)
> skb->prev RSC chains.
> 
> But nothing other than the RX ring shutdown code processes them, so
> likely if they are created they will leak or cause some other kind of
> problem.
> 
> Please take a look, thanks.
> 
> diff --git a/drivers/net/ixgbe/ixgbe.h b/drivers/net/ixgbe/ixgbe.h
> index 5cebc37..434c9fa 100644
> --- a/drivers/net/ixgbe/ixgbe.h
> +++ b/drivers/net/ixgbe/ixgbe.h
> @@ -181,6 +181,7 @@ struct ixgbe_ring {
>  	struct ixgbe_queue_stats stats;
>  	unsigned long reinit_state;
>  	int numa_node;
> +	struct sk_buff *rsc_skb;	/* RSC packet being built */
>  	u64 rsc_count;			/* stat for coalesced packets */
>  	u64 rsc_flush;			/* stats for flushed packets */
>  	u32 restart_queue;		/* track tx queue restarts */
> diff --git a/drivers/net/ixgbe/ixgbe_main.c b/drivers/net/ixgbe/ixgbe_main.c
> index c35e13c..43987a4 100644
> --- a/drivers/net/ixgbe/ixgbe_main.c
> +++ b/drivers/net/ixgbe/ixgbe_main.c
> @@ -1130,36 +1130,6 @@ static inline u32 ixgbe_get_rsc_count(union
> ixgbe_adv_rx_desc *rx_desc)
>  		IXGBE_RXDADV_RSCCNT_SHIFT;
>  }
> 
> -/**
> - * ixgbe_transform_rsc_queue - change rsc queue into a full packet
> - * @skb: pointer to the last skb in the rsc queue
> - * @count: pointer to number of packets coalesced in this context
> - *
> - * This function changes a queue full of hw rsc buffers into a completed
> - * packet.  It uses the ->prev pointers to find the first packet and then
> - * turns it into the frag list owner.
> - **/
> -static inline struct sk_buff *ixgbe_transform_rsc_queue(struct sk_buff *skb,
> -							u64 *count)
> -{
> -	unsigned int frag_list_size = 0;
> -
> -	while (skb->prev) {
> -		struct sk_buff *prev = skb->prev;
> -		frag_list_size += skb->len;
> -		skb->prev = NULL;
> -		skb = prev;
> -		*count += 1;
> -	}
> -
> -	skb_shinfo(skb)->frag_list = skb->next;
> -	skb->next = NULL;
> -	skb->len += frag_list_size;
> -	skb->data_len += frag_list_size;
> -	skb->truesize += frag_list_size;
> -	return skb;
> -}
> -
>  struct ixgbe_rsc_cb {
>  	dma_addr_t dma;
>  	bool delay_unmap;
> @@ -1216,10 +1186,13 @@ static bool ixgbe_clean_rx_irq(struct ixgbe_q_vector
> *q_vector,
>  		prefetch(skb->data);
>  		rx_buffer_info->skb = NULL;
> 
> +		if (!(staterr & IXGBE_RXD_STAT_EOP)) {
> +			rx_ring->rsc_skb = skb;
> +			rx_ring->rsc_count++;
> +		}
>  		if (rx_buffer_info->dma) {
>  			if ((adapter->flags2 & IXGBE_FLAG2_RSC_ENABLED) &&
> -			    (!(staterr & IXGBE_RXD_STAT_EOP)) &&
> -				 (!(skb->prev))) {
> +			    rx_ring->rsc_skb == skb) {
>  				/*
>  				 * When HWRSC is enabled, delay unmapping
>  				 * of the first packet. It carries the
> @@ -1279,9 +1252,12 @@ static bool ixgbe_clean_rx_irq(struct ixgbe_q_vector
> *q_vector,
>  		}
> 
>  		if (staterr & IXGBE_RXD_STAT_EOP) {
> -			if (skb->prev)
> -				skb = ixgbe_transform_rsc_queue(skb,
> -
> 	&(rx_ring->rsc_count));
> +			if (rx_ring->rsc_skb) {
> +				skb = rx_ring->rsc_skb;
> +				rx_ring->rsc_skb = NULL;
> +
> +				skb->prev = NULL;
> +			}
>  			if (adapter->flags2 & IXGBE_FLAG2_RSC_ENABLED) {
>  				if (IXGBE_RSC_CB(skb)->delay_unmap) {
>  					dma_unmap_single(&pdev->dev,
> @@ -1307,8 +1283,22 @@ static bool ixgbe_clean_rx_irq(struct ixgbe_q_vector
> *q_vector,
>  				next_buffer->skb = skb;
>  				next_buffer->dma = 0;
>  			} else {
> -				skb->next = next_buffer->skb;
> -				skb->next->prev = skb;
> +				struct sk_buff *head = rx_ring->rsc_skb;
> +				struct sk_buff *next = next_buffer->skb;
> +
> +				if (!skb_shinfo(head)->frag_list) {
> +					skb_shinfo(head)->frag_list =
> next;
> +				} else {
> +					head->prev->next = next;
> +				}
> +
> +				/* ->prev tracks the last skb in the
> frag_list */
> +				head->prev = next;
> +				rx_ring->rsc_count++;
> +
> +				head->len += next->len;
> +				head->data_len += next->len;
> +				head->truesize += next->len;
>  			}
>  			rx_ring->non_eop_descs++;
>  			goto next_desc;
> --
> To unsubscribe from this list: send the line "unsubscribe netdev" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [PATCH net-next V3] net: dynamic ingress_queue allocation
From: Eric Dumazet @ 2010-10-04 15:21 UTC (permalink / raw)
  To: Jarek Poplawski; +Cc: hadi, David Miller, netdev
In-Reply-To: <20101004142415.GA2049@del.dom.local>

Le lundi 04 octobre 2010 à 16:24 +0200, Jarek Poplawski a écrit :

> You are right! There is no other lock. (I forgot I removed
> qdisc_list_lock already ;-)
> 
> Sorry,

No problem, better to ask before inclusion anyway ;)

About checkpatch, patch is fine (checkpatch.pl shouts because of yet
unknown __rcu marker, I believe someone submitted a patch for this [and
__percpu] weeks ago, but lkml people seem very slow these days)

ERROR: need consistent spacing around '*' (ctx:WxV)
#10: FILE: include/linux/netdevice.h:989:
+	struct netdev_queue __rcu *ingress_queue;
 	                          ^
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 2039acd..25d3842 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -143,7 +143,9 @@ our $Sparse	= qr{
 			__must_check|
 			__init_refok|
 			__kprobes|
-			__ref
+			__ref|
+			__percpu|
+			__rcu
 		}x;
 
 # Notes to $Attribute:



^ permalink raw reply related

* Re: [PATCH] Add Qualcomm Gobi 2000 driver.
From: Elly Jones @ 2010-10-04 15:03 UTC (permalink / raw)
  To: Dan Williams; +Cc: netdev, dbrownell, mjg59, jglasgow, msb, olofj
In-Reply-To: <1285708983.4378.48.camel@dcbw.foobar.com>

On Tue, Sep 28, 2010 at 04:23:03PM -0500, Dan Williams wrote:
> 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/).
> 
> Also, is there any way you can wrangle Gobi 1000 support out of
> Qualcomm?  I tried the driver with a 1000 part when Qualcomm originally
> posted the 2K net driver back in June and it resulted in a panic when
> the driver didn't get the response it expected.  I suppose I can try
> again with this code too, but getting some feedback from Qualcomm that
> 1K parts might work here would be nice too.

Sorry, this one fell on the floor. We don't know much about the Gobi 1000 and we
do not have an example to test with, which means that owning a driver for it
would be difficult.
 
> Also, are 3K devices supported by this code?  The 3K net and serial
> driver was recently posted on codeaurora.

No. I've looked at the 3K changes, and it seems like it's mostly a few new
vid:pid pairs. I will look into this once I get a 3K to test with.

> Dan

-- Elly

> 
> 
> > 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(&notify->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, &current->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, &current->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 */
> 
> 

^ permalink raw reply

* [PATCH] caif: remove duplicated include
From: Nicolas Kaiser @ 2010-10-04 14:35 UTC (permalink / raw)
  To: Sjur Braendeland; +Cc: netdev, linux-kernel

Remove duplicated include.

Signed-off-by: Nicolas Kaiser <nikai@nikai.net>
---
 net/caif/caif_socket.c |    1 -
 1 files changed, 0 insertions(+), 1 deletions(-)

diff --git a/net/caif/caif_socket.c b/net/caif/caif_socket.c
index 8ce9047..7a09fb6 100644
--- a/net/caif/caif_socket.c
+++ b/net/caif/caif_socket.c
@@ -15,7 +15,6 @@
 #include <linux/poll.h>
 #include <linux/tcp.h>
 #include <linux/uaccess.h>
-#include <linux/mutex.h>
 #include <linux/debugfs.h>
 #include <linux/caif/caif_socket.h>
 #include <asm/atomic.h>
-- 
1.7.2.2

^ permalink raw reply related

* Software iWARP (SIW) patches
From: Bernard Metzler @ 2010-10-04 14:27 UTC (permalink / raw)
  To: netdev-u79uwXL29TY76Z2rM5mHXA, Nicholas A. Bellinger, Steve Wise,
	Roland Dreier
  Cc: Andi Kleen, David Miller, linux-rdma-u79uwXL29TY76Z2rM5mHXA,
	linux-rdma-owner-u79uwXL29TY76Z2rM5mHXA, Matthew Wilcox
In-Reply-To: <1285187710.1849.82.camel-Y1+j5t8j3WgjMeEPmliV8E/sVC8ogwMJ@public.gmane.org>

All,
many thanks for your suggestions and help in preparing a set of
patches to add an in-kernel, full-software iWARP stack. As suggested,
the patches are against the current net-next-2.6.git repository,
all version dependencies etc. are removed. To avoid redundancy,
the patches will be posted to the netdev list only. Please keep
me on cc on any replies, since I am not subscribed to netdev.

We post the patches in the hope to make a useful contribution
to the Linux networking stack, and we hope to see SoftiWARP
becoming part of a future mainline Linux. Of course we are very open
to all discussion, suggestions and comments to improve quality and
completeness of the current code. Many thanks in advance!

We will keep www.gitorious.org/softiwarp updated to make a version
of SoftiWARP kernel and user code available for immediate testing
on a running system.
The code at gitorious differs from the current patch in its extensions
to support older kernel versions. All these extensions are #ifdef'ed
and are not part of the upcoming patch series.
www.gitorious.org/softiwarp/userlib remains the current source of the
user library for SoftiWARP.

Please find below both git shortlog and diffstat for overview.


Many Thanks,
Bernard.
--

Bernard Metzler (13):
      SIW: Kconfig and Makefile
      SIW: iWARP Protocol headers
      SIW: Main header file
      SIW: Module initialization
      SIW: User interface
      SIW: Connection management
      SIW: Object management
      SIW: Queue pair
      SIW: Completion queue
      SIW: Transmit path
      SIW: Receive path
      SIW: Debugging and Tracing
      SIW: Documentation (initial)

 Documentation/networking/siw.txt      |   91 ++
 drivers/infiniband/Kconfig            |    1 +
 drivers/infiniband/Makefile           |    1 +
 drivers/infiniband/hw/siw/Kconfig     |   14 +
 drivers/infiniband/hw/siw/Makefile    |    5 +
 drivers/infiniband/hw/siw/iwarp.h     |  324 ++++++
 drivers/infiniband/hw/siw/siw.h       |  816 ++++++++++++++
 drivers/infiniband/hw/siw/siw_ae.c    |   96 ++
 drivers/infiniband/hw/siw/siw_cm.c    | 1939 +++++++++++++++++++++++++++++
++++
 drivers/infiniband/hw/siw/siw_cm.h    |  155 +++
 drivers/infiniband/hw/siw/siw_cq.c    |  243 ++++
 drivers/infiniband/hw/siw/siw_debug.c |  198 ++++
 drivers/infiniband/hw/siw/siw_debug.h |  159 +++
 drivers/infiniband/hw/siw/siw_main.c  |  440 ++++++++
 drivers/infiniband/hw/siw/siw_obj.c   |  499 +++++++++
 drivers/infiniband/hw/siw/siw_obj.h   |  109 ++
 drivers/infiniband/hw/siw/siw_qp.c    |  989 +++++++++++++++++
 drivers/infiniband/hw/siw/siw_qp_rx.c | 1493 +++++++++++++++++++++++++
 drivers/infiniband/hw/siw/siw_qp_tx.c | 1309 ++++++++++++++++++++++
 drivers/infiniband/hw/siw/siw_user.h  |   66 ++
 drivers/infiniband/hw/siw/siw_verbs.c | 1564 ++++++++++++++++++++++++++
 drivers/infiniband/hw/siw/siw_verbs.h |   96 ++
 22 files changed, 10607 insertions(+), 0 deletions(-)

--
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 net-next] net neigh: neigh_delete() and neigh_add() changes
From: Eric Dumazet @ 2010-10-04 14:27 UTC (permalink / raw)
  To: David Miller; +Cc: netdev

neigh_delete() and neigh_add() dont need to touch device refcount,
we hold RTNL when calling them, so device cannot disappear under us.

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
---
 net/core/neighbour.c |   39 +++++++++++++++++----------------------
 1 files changed, 17 insertions(+), 22 deletions(-)

diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index b142a0d..d6996e0 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c
@@ -1531,6 +1531,7 @@ static int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
 	struct net_device *dev = NULL;
 	int err = -EINVAL;
 
+	ASSERT_RTNL();
 	if (nlmsg_len(nlh) < sizeof(*ndm))
 		goto out;
 
@@ -1540,7 +1541,7 @@ static int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
 
 	ndm = nlmsg_data(nlh);
 	if (ndm->ndm_ifindex) {
-		dev = dev_get_by_index(net, ndm->ndm_ifindex);
+		dev = __dev_get_by_index(net, ndm->ndm_ifindex);
 		if (dev == NULL) {
 			err = -ENODEV;
 			goto out;
@@ -1556,34 +1557,31 @@ static int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
 		read_unlock(&neigh_tbl_lock);
 
 		if (nla_len(dst_attr) < tbl->key_len)
-			goto out_dev_put;
+			goto out;
 
 		if (ndm->ndm_flags & NTF_PROXY) {
 			err = pneigh_delete(tbl, net, nla_data(dst_attr), dev);
-			goto out_dev_put;
+			goto out;
 		}
 
 		if (dev == NULL)
-			goto out_dev_put;
+			goto out;
 
 		neigh = neigh_lookup(tbl, nla_data(dst_attr), dev);
 		if (neigh == NULL) {
 			err = -ENOENT;
-			goto out_dev_put;
+			goto out;
 		}
 
 		err = neigh_update(neigh, NULL, NUD_FAILED,
 				   NEIGH_UPDATE_F_OVERRIDE |
 				   NEIGH_UPDATE_F_ADMIN);
 		neigh_release(neigh);
-		goto out_dev_put;
+		goto out;
 	}
 	read_unlock(&neigh_tbl_lock);
 	err = -EAFNOSUPPORT;
 
-out_dev_put:
-	if (dev)
-		dev_put(dev);
 out:
 	return err;
 }
@@ -1597,6 +1595,7 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
 	struct net_device *dev = NULL;
 	int err;
 
+	ASSERT_RTNL();
 	err = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX, NULL);
 	if (err < 0)
 		goto out;
@@ -1607,14 +1606,14 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
 
 	ndm = nlmsg_data(nlh);
 	if (ndm->ndm_ifindex) {
-		dev = dev_get_by_index(net, ndm->ndm_ifindex);
+		dev = __dev_get_by_index(net, ndm->ndm_ifindex);
 		if (dev == NULL) {
 			err = -ENODEV;
 			goto out;
 		}
 
 		if (tb[NDA_LLADDR] && nla_len(tb[NDA_LLADDR]) < dev->addr_len)
-			goto out_dev_put;
+			goto out;
 	}
 
 	read_lock(&neigh_tbl_lock);
@@ -1628,7 +1627,7 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
 		read_unlock(&neigh_tbl_lock);
 
 		if (nla_len(tb[NDA_DST]) < tbl->key_len)
-			goto out_dev_put;
+			goto out;
 		dst = nla_data(tb[NDA_DST]);
 		lladdr = tb[NDA_LLADDR] ? nla_data(tb[NDA_LLADDR]) : NULL;
 
@@ -1641,29 +1640,29 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
 				pn->flags = ndm->ndm_flags;
 				err = 0;
 			}
-			goto out_dev_put;
+			goto out;
 		}
 
 		if (dev == NULL)
-			goto out_dev_put;
+			goto out;
 
 		neigh = neigh_lookup(tbl, dst, dev);
 		if (neigh == NULL) {
 			if (!(nlh->nlmsg_flags & NLM_F_CREATE)) {
 				err = -ENOENT;
-				goto out_dev_put;
+				goto out;
 			}
 
 			neigh = __neigh_lookup_errno(tbl, dst, dev);
 			if (IS_ERR(neigh)) {
 				err = PTR_ERR(neigh);
-				goto out_dev_put;
+				goto out;
 			}
 		} else {
 			if (nlh->nlmsg_flags & NLM_F_EXCL) {
 				err = -EEXIST;
 				neigh_release(neigh);
-				goto out_dev_put;
+				goto out;
 			}
 
 			if (!(nlh->nlmsg_flags & NLM_F_REPLACE))
@@ -1676,15 +1675,11 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
 		} else
 			err = neigh_update(neigh, lladdr, ndm->ndm_state, flags);
 		neigh_release(neigh);
-		goto out_dev_put;
+		goto out;
 	}
 
 	read_unlock(&neigh_tbl_lock);
 	err = -EAFNOSUPPORT;
-
-out_dev_put:
-	if (dev)
-		dev_put(dev);
 out:
 	return err;
 }



^ permalink raw reply related

* Re: [PATCH net-next V3] net: dynamic ingress_queue allocation
From: Jarek Poplawski @ 2010-10-04 14:24 UTC (permalink / raw)
  To: Eric Dumazet; +Cc: hadi, David Miller, netdev
In-Reply-To: <1286196720.18293.196.camel@edumazet-laptop>

On Mon, Oct 04, 2010 at 02:52:00PM +0200, Eric Dumazet wrote:
> Le lundi 04 octobre 2010 ?? 14:06 +0200, Jarek Poplawski a écrit :
> 
> > Right, but then at least in qdisc_lookup():
> > 
> > if (dev_ingress_queue(dev))
> > 	q = qdisc_match_from_root(dev_ingress_queue(dev), handle);
> > 
> > you should use a variable instead of the second dereference (rtnl isn't
> > mandatory here).
> 
> I am lost.
> 
> If rntl is not mandatory, what is the lock that protects us ?
> 
> qdisc_lookup() _is_ called under the protection of a lock.
> Or as soon as we return from it, result could change under us.
> 
> Please name it, and I'll use it :
> 
> rcu_dereference_protected(dev->ingress_queue, lockdep_is_held(THISLOCK))
> 

You are right! There is no other lock. (I forgot I removed
qdisc_list_lock already ;-)

Sorry,
Jarek P.

^ permalink raw reply

* [PATCHv2 NEXT 8/8] qlcnic: set mtu lower limit
From: Amit Kumar Salecha @ 2010-10-04 14:20 UTC (permalink / raw)
  To: davem; +Cc: netdev, ameen.rahman, anirban.chakraborty, Sritej Velaga
In-Reply-To: <1286202016-18026-1-git-send-email-amit.salecha@qlogic.com>

From: Sritej Velaga <sritej.velaga@qlogic.com>

Setting mtu < 68 is not supported.

Signed-off-by: Sritej Velaga <sritej.velaga@qlogic.com>
Signed-off-by: Amit Kumar Salecha <amit.salecha@qlogic.com>
---
 drivers/net/qlcnic/qlcnic.h    |    1 +
 drivers/net/qlcnic/qlcnic_hw.c |    6 +++---
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/drivers/net/qlcnic/qlcnic.h b/drivers/net/qlcnic/qlcnic.h
index 9d80171..42a2883 100644
--- a/drivers/net/qlcnic/qlcnic.h
+++ b/drivers/net/qlcnic/qlcnic.h
@@ -95,6 +95,7 @@
 #define FIRST_PAGE_GROUP_END	0x100000
 
 #define P3_MAX_MTU                     (9600)
+#define P3_MIN_MTU                     (68)
 #define QLCNIC_MAX_ETHERHDR                32 /* This contains some padding */
 
 #define QLCNIC_P3_RX_BUF_MAX_LEN         (QLCNIC_MAX_ETHERHDR + ETH_DATA_LEN)
diff --git a/drivers/net/qlcnic/qlcnic_hw.c b/drivers/net/qlcnic/qlcnic_hw.c
index 712cfab..9d3e16d 100644
--- a/drivers/net/qlcnic/qlcnic_hw.c
+++ b/drivers/net/qlcnic/qlcnic_hw.c
@@ -758,9 +758,9 @@ int qlcnic_change_mtu(struct net_device *netdev, int mtu)
 	struct qlcnic_adapter *adapter = netdev_priv(netdev);
 	int rc = 0;
 
-	if (mtu > P3_MAX_MTU) {
-		dev_err(&adapter->netdev->dev, "mtu > %d bytes unsupported\n",
-						P3_MAX_MTU);
+	if (mtu < P3_MIN_MTU || mtu > P3_MAX_MTU) {
+		dev_err(&adapter->netdev->dev, "%d bytes < mtu < %d bytes"
+			" not supported\n", P3_MAX_MTU, P3_MIN_MTU);
 		return -EINVAL;
 	}
 
-- 
1.6.3.3


^ permalink raw reply related

* [PATCHv2 NEXT 5/8] qlcnic: fix vlan TSO on big endian machine
From: Amit Kumar Salecha @ 2010-10-04 14:20 UTC (permalink / raw)
  To: davem; +Cc: netdev, ameen.rahman, anirban.chakraborty, Sucheta Chakraborty
In-Reply-To: <1286202016-18026-1-git-send-email-amit.salecha@qlogic.com>

From: Sucheta Chakraborty <sucheta.chakraborty@qlogic.com>

o desc->vlan_tci is in __le16 format. Doing htons and
  cpu_to_le64 again on vlan_tci, result in invalid value on ppc.

Signed-off-by: Sucheta Chakraborty <sucheta.chakraborty@qlogic.com>
Signed-off-by: Amit Kumar Salecha <amit.salecha@qlogic.com>
---
 drivers/net/qlcnic/qlcnic.h      |    7 ++++++-
 drivers/net/qlcnic/qlcnic_hw.c   |    6 ++++--
 drivers/net/qlcnic/qlcnic_main.c |   15 ++++++++++-----
 3 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/drivers/net/qlcnic/qlcnic.h b/drivers/net/qlcnic/qlcnic.h
index 7af3c6c..9d80171 100644
--- a/drivers/net/qlcnic/qlcnic.h
+++ b/drivers/net/qlcnic/qlcnic.h
@@ -898,6 +898,11 @@ struct qlcnic_mac_req {
 	u8 mac_addr[6];
 };
 
+struct qlcnic_vlan_req {
+	__le16 vlan_id;
+	__le16 rsvd[3];
+};
+
 struct qlcnic_ipaddr {
 	__be32 ipv4;
 	__be32 ipv6[4];
@@ -940,7 +945,7 @@ struct qlcnic_ipaddr {
 struct qlcnic_filter {
 	struct hlist_node fnode;
 	u8 faddr[ETH_ALEN];
-	u16 vlan_id;
+	__le16 vlan_id;
 	unsigned long ftime;
 };
 
diff --git a/drivers/net/qlcnic/qlcnic_hw.c b/drivers/net/qlcnic/qlcnic_hw.c
index 68d5693..712cfab 100644
--- a/drivers/net/qlcnic/qlcnic_hw.c
+++ b/drivers/net/qlcnic/qlcnic_hw.c
@@ -375,10 +375,11 @@ qlcnic_send_cmd_descs(struct qlcnic_adapter *adapter,
 
 static int
 qlcnic_sre_macaddr_change(struct qlcnic_adapter *adapter, u8 *addr,
-				u16 vlan_id, unsigned op)
+				__le16 vlan_id, unsigned op)
 {
 	struct qlcnic_nic_req req;
 	struct qlcnic_mac_req *mac_req;
+	struct qlcnic_vlan_req *vlan_req;
 	u64 word;
 
 	memset(&req, 0, sizeof(struct qlcnic_nic_req));
@@ -391,7 +392,8 @@ qlcnic_sre_macaddr_change(struct qlcnic_adapter *adapter, u8 *addr,
 	mac_req->op = op;
 	memcpy(mac_req->mac_addr, addr, 6);
 
-	req.words[1] = cpu_to_le64(vlan_id);
+	vlan_req = (struct qlcnic_vlan_req *)&req.words[1];
+	vlan_req->vlan_id = vlan_id;
 
 	return qlcnic_send_cmd_descs(adapter, (struct cmd_desc_type0 *)&req, 1);
 }
diff --git a/drivers/net/qlcnic/qlcnic_main.c b/drivers/net/qlcnic/qlcnic_main.c
index a3d7705..6001f41 100644
--- a/drivers/net/qlcnic/qlcnic_main.c
+++ b/drivers/net/qlcnic/qlcnic_main.c
@@ -28,6 +28,7 @@
 
 #include "qlcnic.h"
 
+#include <linux/swab.h>
 #include <linux/dma-mapping.h>
 #include <linux/if_vlan.h>
 #include <net/ip.h>
@@ -1834,11 +1835,12 @@ static void qlcnic_free_lb_filters_mem(struct qlcnic_adapter *adapter)
 }
 
 static void qlcnic_change_filter(struct qlcnic_adapter *adapter,
-		u64 uaddr, u16 vlan_id, struct qlcnic_host_tx_ring *tx_ring)
+		u64 uaddr, __le16 vlan_id, struct qlcnic_host_tx_ring *tx_ring)
 {
 	struct cmd_desc_type0 *hwdesc;
 	struct qlcnic_nic_req *req;
 	struct qlcnic_mac_req *mac_req;
+	struct qlcnic_vlan_req *vlan_req;
 	u32 producer;
 	u64 word;
 
@@ -1856,7 +1858,8 @@ static void qlcnic_change_filter(struct qlcnic_adapter *adapter,
 	mac_req->op = vlan_id ? QLCNIC_MAC_VLAN_ADD : QLCNIC_MAC_ADD;
 	memcpy(mac_req->mac_addr, &uaddr, ETH_ALEN);
 
-	req->words[1] = cpu_to_le64(vlan_id);
+	vlan_req = (struct qlcnic_vlan_req *)&req->words[1];
+	vlan_req->vlan_id = vlan_id;
 
 	tx_ring->producer = get_next_index(producer, tx_ring->num_desc);
 }
@@ -1875,7 +1878,7 @@ qlcnic_send_filter(struct qlcnic_adapter *adapter,
 	struct hlist_node *tmp_hnode, *n;
 	struct hlist_head *head;
 	u64 src_addr = 0;
-	u16 vlan_id = 0;
+	__le16 vlan_id = 0;
 	u8 hindex;
 
 	if (!compare_ether_addr(phdr->h_source, adapter->mac_addr))
@@ -1928,7 +1931,8 @@ qlcnic_tso_check(struct net_device *netdev,
 	struct vlan_ethhdr *vh;
 	struct qlcnic_adapter *adapter = netdev_priv(netdev);
 	u32 producer = tx_ring->producer;
-	int vlan_oob = first_desc->flags_opcode & cpu_to_le16(FLAGS_VLAN_OOB);
+	__le16 vlan_oob = first_desc->flags_opcode &
+				cpu_to_le16(FLAGS_VLAN_OOB);
 
 	if (*(skb->data) & BIT_0) {
 		flags |= BIT_0;
@@ -1999,7 +2003,8 @@ qlcnic_tso_check(struct net_device *netdev,
 		vh = (struct vlan_ethhdr *)((char *)hwdesc + 2);
 		skb_copy_from_linear_data(skb, vh, 12);
 		vh->h_vlan_proto = htons(ETH_P_8021Q);
-		vh->h_vlan_TCI = htons(first_desc->vlan_TCI);
+		vh->h_vlan_TCI = (__be16)swab16((u16)first_desc->vlan_TCI);
+
 		skb_copy_from_linear_data_offset(skb, 12,
 				(char *)vh + 16, copy_len - 16);
 
-- 
1.6.3.3


^ permalink raw reply related

* [PATCHv2 NEXT 6/8] qlcnic: sparse warning fixes
From: Amit Kumar Salecha @ 2010-10-04 14:20 UTC (permalink / raw)
  To: davem; +Cc: netdev, ameen.rahman, anirban.chakraborty, Sucheta Chakraborty
In-Reply-To: <1286202016-18026-1-git-send-email-amit.salecha@qlogic.com>

From: Sucheta Chakraborty <sucheta.chakraborty@qlogic.com>

Signed-off-by: Sucheta Chakraborty <sucheta.chakraborty@qlogic.com>
Signed-off-by: Amit Kumar Salecha <amit.salecha@qlogic.com>
---
 drivers/net/qlcnic/qlcnic_ctx.c  |   12 ++++++------
 drivers/net/qlcnic/qlcnic_main.c |   10 +++++-----
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/drivers/net/qlcnic/qlcnic_ctx.c b/drivers/net/qlcnic/qlcnic_ctx.c
index a4c4d09..75e3b19 100644
--- a/drivers/net/qlcnic/qlcnic_ctx.c
+++ b/drivers/net/qlcnic/qlcnic_ctx.c
@@ -742,15 +742,15 @@ int qlcnic_get_pci_info(struct qlcnic_adapter *adapter,
 
 	if (err == QLCNIC_RCODE_SUCCESS) {
 		for (i = 0; i < QLCNIC_MAX_PCI_FUNC; i++, npar++, pci_info++) {
-			pci_info->id = le32_to_cpu(npar->id);
-			pci_info->active = le32_to_cpu(npar->active);
-			pci_info->type = le32_to_cpu(npar->type);
+			pci_info->id = le16_to_cpu(npar->id);
+			pci_info->active = le16_to_cpu(npar->active);
+			pci_info->type = le16_to_cpu(npar->type);
 			pci_info->default_port =
-				le32_to_cpu(npar->default_port);
+				le16_to_cpu(npar->default_port);
 			pci_info->tx_min_bw =
-				le32_to_cpu(npar->tx_min_bw);
+				le16_to_cpu(npar->tx_min_bw);
 			pci_info->tx_max_bw =
-				le32_to_cpu(npar->tx_max_bw);
+				le16_to_cpu(npar->tx_max_bw);
 			memcpy(pci_info->mac, npar->mac, ETH_ALEN);
 		}
 	} else {
diff --git a/drivers/net/qlcnic/qlcnic_main.c b/drivers/net/qlcnic/qlcnic_main.c
index 6001f41..59a2138 100644
--- a/drivers/net/qlcnic/qlcnic_main.c
+++ b/drivers/net/qlcnic/qlcnic_main.c
@@ -521,9 +521,9 @@ qlcnic_init_pci_info(struct qlcnic_adapter *adapter)
 		pfn = pci_info[i].id;
 		if (pfn > QLCNIC_MAX_PCI_FUNC)
 			return QL_STATUS_INVALID_PARAM;
-		adapter->npars[pfn].active = pci_info[i].active;
-		adapter->npars[pfn].type = pci_info[i].type;
-		adapter->npars[pfn].phy_port = pci_info[i].default_port;
+		adapter->npars[pfn].active = (u8)pci_info[i].active;
+		adapter->npars[pfn].type = (u8)pci_info[i].type;
+		adapter->npars[pfn].phy_port = (u8)pci_info[i].default_port;
 		adapter->npars[pfn].min_bw = pci_info[i].tx_min_bw;
 		adapter->npars[pfn].max_bw = pci_info[i].tx_max_bw;
 	}
@@ -723,7 +723,7 @@ qlcnic_initialize_nic(struct qlcnic_adapter *adapter)
 	if (err)
 		return err;
 
-	adapter->physical_port = nic_info.phys_port;
+	adapter->physical_port = (u8)nic_info.phys_port;
 	adapter->switch_mode = nic_info.switch_mode;
 	adapter->max_tx_ques = nic_info.max_tx_ques;
 	adapter->max_rx_ques = nic_info.max_rx_ques;
@@ -3762,7 +3762,7 @@ qlcnic_sysfs_read_npar_config(struct file *file, struct kobject *kobj,
 			return ret;
 
 		np_cfg[i].pci_func = i;
-		np_cfg[i].op_mode = nic_info.op_mode;
+		np_cfg[i].op_mode = (u8)nic_info.op_mode;
 		np_cfg[i].port_num = nic_info.phys_port;
 		np_cfg[i].fw_capab = nic_info.capabilities;
 		np_cfg[i].min_bw = nic_info.min_tx_bw ;
-- 
1.6.3.3


^ permalink raw reply related

* [PATCHv2 NEXT 4/8] qlcnic: fix endianess for lro
From: Amit Kumar Salecha @ 2010-10-04 14:20 UTC (permalink / raw)
  To: davem; +Cc: netdev, ameen.rahman, anirban.chakraborty, Sucheta Chakraborty
In-Reply-To: <1286202016-18026-1-git-send-email-amit.salecha@qlogic.com>

From: Sucheta Chakraborty <sucheta.chakraborty@qlogic.com>

ipaddress in ifa->ifa_address field are in big endian format.
Also device requires ip address in big endian only.

Signed-off-by: Sucheta Chakraborty <sucheta.chakraborty@qlogic.com>
Signed-off-by: Amit Kumar Salecha <amit.salecha@qlogic.com>
---
 drivers/net/qlcnic/qlcnic.h    |    7 ++++++-
 drivers/net/qlcnic/qlcnic_hw.c |    6 ++++--
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/drivers/net/qlcnic/qlcnic.h b/drivers/net/qlcnic/qlcnic.h
index 4667463..7af3c6c 100644
--- a/drivers/net/qlcnic/qlcnic.h
+++ b/drivers/net/qlcnic/qlcnic.h
@@ -898,6 +898,11 @@ struct qlcnic_mac_req {
 	u8 mac_addr[6];
 };
 
+struct qlcnic_ipaddr {
+	__be32 ipv4;
+	__be32 ipv6[4];
+};
+
 #define QLCNIC_MSI_ENABLED		0x02
 #define QLCNIC_MSIX_ENABLED		0x04
 #define QLCNIC_LRO_ENABLED		0x08
@@ -1286,7 +1291,7 @@ void qlcnic_free_mac_list(struct qlcnic_adapter *adapter);
 int qlcnic_nic_set_promisc(struct qlcnic_adapter *adapter, u32);
 int qlcnic_config_intr_coalesce(struct qlcnic_adapter *adapter);
 int qlcnic_config_rss(struct qlcnic_adapter *adapter, int enable);
-int qlcnic_config_ipaddr(struct qlcnic_adapter *adapter, u32 ip, int cmd);
+int qlcnic_config_ipaddr(struct qlcnic_adapter *adapter, __be32 ip, int cmd);
 int qlcnic_linkevent_request(struct qlcnic_adapter *adapter, int enable);
 void qlcnic_advert_link_change(struct qlcnic_adapter *adapter, int linkup);
 
diff --git a/drivers/net/qlcnic/qlcnic_hw.c b/drivers/net/qlcnic/qlcnic_hw.c
index c198df9..68d5693 100644
--- a/drivers/net/qlcnic/qlcnic_hw.c
+++ b/drivers/net/qlcnic/qlcnic_hw.c
@@ -676,9 +676,10 @@ int qlcnic_config_rss(struct qlcnic_adapter *adapter, int enable)
 	return rv;
 }
 
-int qlcnic_config_ipaddr(struct qlcnic_adapter *adapter, u32 ip, int cmd)
+int qlcnic_config_ipaddr(struct qlcnic_adapter *adapter, __be32 ip, int cmd)
 {
 	struct qlcnic_nic_req req;
+	struct qlcnic_ipaddr *ipa;
 	u64 word;
 	int rv;
 
@@ -689,7 +690,8 @@ int qlcnic_config_ipaddr(struct qlcnic_adapter *adapter, u32 ip, int cmd)
 	req.req_hdr = cpu_to_le64(word);
 
 	req.words[0] = cpu_to_le64(cmd);
-	req.words[1] = cpu_to_le64(ip);
+	ipa = (struct qlcnic_ipaddr *)&req.words[1];
+	ipa->ipv4 = ip;
 
 	rv = qlcnic_send_cmd_descs(adapter, (struct cmd_desc_type0 *)&req, 1);
 	if (rv != 0)
-- 
1.6.3.3


^ permalink raw reply related

* [PATCHv2 NEXT 7/8] qlcnic: cleanup port mode setting
From: Amit Kumar Salecha @ 2010-10-04 14:20 UTC (permalink / raw)
  To: davem; +Cc: netdev, ameen.rahman, anirban.chakraborty, Sritej Velaga
In-Reply-To: <1286202016-18026-1-git-send-email-amit.salecha@qlogic.com>

From: Sritej Velaga <sritej.velaga@qlogic.com>

Port mode setting is not required for Qlogic CNA adapters.

Signed-off-by: Sritej Velaga <sritej.velaga@qlogic.com>
Signed-off-by: Amit Kumar Salecha <amit.salecha@qlogic.com>
---
 drivers/net/qlcnic/qlcnic_main.c |   40 --------------------------------------
 1 files changed, 0 insertions(+), 40 deletions(-)

diff --git a/drivers/net/qlcnic/qlcnic_main.c b/drivers/net/qlcnic/qlcnic_main.c
index 59a2138..4757908 100644
--- a/drivers/net/qlcnic/qlcnic_main.c
+++ b/drivers/net/qlcnic/qlcnic_main.c
@@ -46,11 +46,6 @@ char qlcnic_driver_name[] = "qlcnic";
 static const char qlcnic_driver_string[] = "QLogic 1/10 GbE "
 	"Converged/Intelligent Ethernet Driver v" QLCNIC_LINUX_VERSIONID;
 
-static int port_mode = QLCNIC_PORT_MODE_AUTO_NEG;
-
-/* Default to restricted 1G auto-neg mode */
-static int wol_port_mode = 5;
-
 static int qlcnic_mac_learn;
 module_param(qlcnic_mac_learn, int, 0644);
 MODULE_PARM_DESC(qlcnic_mac_learn, "Mac Filter (0=disabled, 1=enabled)");
@@ -264,40 +259,6 @@ static void qlcnic_clear_stats(struct qlcnic_adapter *adapter)
 	memset(&adapter->stats, 0, sizeof(adapter->stats));
 }
 
-static void qlcnic_set_port_mode(struct qlcnic_adapter *adapter)
-{
-	u32 val, data;
-
-	val = adapter->ahw.board_type;
-	if ((val == QLCNIC_BRDTYPE_P3_HMEZ) ||
-		(val == QLCNIC_BRDTYPE_P3_XG_LOM)) {
-		if (port_mode == QLCNIC_PORT_MODE_802_3_AP) {
-			data = QLCNIC_PORT_MODE_802_3_AP;
-			QLCWR32(adapter, QLCNIC_PORT_MODE_ADDR, data);
-		} else if (port_mode == QLCNIC_PORT_MODE_XG) {
-			data = QLCNIC_PORT_MODE_XG;
-			QLCWR32(adapter, QLCNIC_PORT_MODE_ADDR, data);
-		} else if (port_mode == QLCNIC_PORT_MODE_AUTO_NEG_1G) {
-			data = QLCNIC_PORT_MODE_AUTO_NEG_1G;
-			QLCWR32(adapter, QLCNIC_PORT_MODE_ADDR, data);
-		} else if (port_mode == QLCNIC_PORT_MODE_AUTO_NEG_XG) {
-			data = QLCNIC_PORT_MODE_AUTO_NEG_XG;
-			QLCWR32(adapter, QLCNIC_PORT_MODE_ADDR, data);
-		} else {
-			data = QLCNIC_PORT_MODE_AUTO_NEG;
-			QLCWR32(adapter, QLCNIC_PORT_MODE_ADDR, data);
-		}
-
-		if ((wol_port_mode != QLCNIC_PORT_MODE_802_3_AP) &&
-			(wol_port_mode != QLCNIC_PORT_MODE_XG) &&
-			(wol_port_mode != QLCNIC_PORT_MODE_AUTO_NEG_1G) &&
-			(wol_port_mode != QLCNIC_PORT_MODE_AUTO_NEG_XG)) {
-			wol_port_mode = QLCNIC_PORT_MODE_AUTO_NEG;
-		}
-		QLCWR32(adapter, QLCNIC_WOL_PORT_MODE, wol_port_mode);
-	}
-}
-
 static void qlcnic_set_msix_bit(struct pci_dev *pdev, int enable)
 {
 	u32 control;
@@ -1032,7 +993,6 @@ qlcnic_start_firmware(struct qlcnic_adapter *adapter)
 	err = qlcnic_pinit_from_rom(adapter);
 	if (err)
 		goto err_out;
-	qlcnic_set_port_mode(adapter);
 
 	err = qlcnic_load_firmware(adapter);
 	if (err)
-- 
1.6.3.3


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox