Netdev List
 help / color / mirror / Atom feed
* Re: [PATCH v4 1/3] kernel/notifier.c: intercepting duplicate registrations to avoid infinite loops
From: Xiaoming Ni @ 2019-09-19 12:55 UTC (permalink / raw)
  To: Greg KH
  Cc: akpm, vvs, torvalds, adobriyan, anna.schumaker, arjan, bfields,
	chuck.lever, davem, jlayton, luto, mingo, Nadia.Derbey, paulmck,
	semen.protsenko, stern, tglx, trond.myklebust, viresh.kumar,
	stable, dylix.dailei, yuehaibing, linux-kernel, linux-nfs, netdev
In-Reply-To: <20190919063615.GA2069346@kroah.com>

On 2019/9/19 14:36, Greg KH wrote:
> On Thu, Sep 19, 2019 at 10:58:06AM +0800, Xiaoming Ni wrote:
>> Registering the same notifier to a hook repeatedly can cause the hook
>> list to form a ring or lose other members of the list.
>>
>> case1: An infinite loop in notifier_chain_register() can cause soft lockup
>>         atomic_notifier_chain_register(&test_notifier_list, &test1);
>>         atomic_notifier_chain_register(&test_notifier_list, &test1);
>>         atomic_notifier_chain_register(&test_notifier_list, &test2);
>>
>> case2: An infinite loop in notifier_chain_register() can cause soft lockup
>>         atomic_notifier_chain_register(&test_notifier_list, &test1);
>>         atomic_notifier_chain_register(&test_notifier_list, &test1);
>>         atomic_notifier_call_chain(&test_notifier_list, 0, NULL);
>>
>> case3: lose other hook test2
>>         atomic_notifier_chain_register(&test_notifier_list, &test1);
>>         atomic_notifier_chain_register(&test_notifier_list, &test2);
>>         atomic_notifier_chain_register(&test_notifier_list, &test1);
>>
>> case4: Unregister returns 0, but the hook is still in the linked list,
>>         and it is not really registered. If you call notifier_call_chain
>>         after ko is unloaded, it will trigger oops.
>>
>> If the system is configured with softlockup_panic and the same
>> hook is repeatedly registered on the panic_notifier_list, it
>> will cause a loop panic.
>>
>> Add a check in notifier_chain_register(),
>> Intercepting duplicate registrations to avoid infinite loops
>>
>> Signed-off-by: Xiaoming Ni <nixiaoming@huawei.com>
>> Reviewed-by: Vasily Averin <vvs@virtuozzo.com>
>> ---
>>  kernel/notifier.c | 5 ++++-
>>  1 file changed, 4 insertions(+), 1 deletion(-)
> 
> <formletter>
> 
> This is not the correct way to submit patches for inclusion in the
> stable kernel tree.  Please read:
>     https://www.kernel.org/doc/html/latest/process/stable-kernel-rules.html
> for how to do this properly.
> 
thanks for your guidance
I thought that as long as the code exists in the stable branch, it should be copied to stable@kernel.org
it is my mistake,

These patches are intended to be sent to the main line.
Should I resend it again?

> </formletter>
> 
> Same thing goes for all of the patches in this series.
> 
> thanks,
> 
> greg k-h
> 
> .
> 

thanks

Xiaoming Ni


^ permalink raw reply

* Re: [PATCH RFC v3 0/5] Support fraglist GRO/GSO
From: Willem de Bruijn @ 2019-09-19 12:55 UTC (permalink / raw)
  To: Marcelo Ricardo Leitner
  Cc: Willem de Bruijn, Steffen Klassert, Network Development,
	Paolo Abeni
In-Reply-To: <20190918165817.GA3431@localhost.localdomain>

On Wed, Sep 18, 2019 at 12:58 PM Marcelo Ricardo Leitner
<marcelo.leitner@gmail.com> wrote:
>
> On Wed, Sep 18, 2019 at 12:17:08PM -0400, Willem de Bruijn wrote:
> > On Wed, Sep 18, 2019 at 3:25 AM Steffen Klassert
> > <steffen.klassert@secunet.com> wrote:
> > >
> > > This patchset adds support to do GRO/GSO by chaining packets
> > > of the same flow at the SKB frag_list pointer. This avoids
> > > the overhead to merge payloads into one big packet, and
> > > on the other end, if GSO is needed it avoids the overhead
> > > of splitting the big packet back to the native form.
> > >
> > > Patch 1 Enables UDP GRO by default.
> > >
> > > Patch 2 adds a netdev feature flag to enable listifyed GRO,
> > > this implements one of the configuration options discussed
> > > at netconf 2019.
> > >
> > > Patch 3 adds a netdev software feature set that defaults to off
> > > and assigns the new listifyed GRO feature flag to it.
> > >
> > > Patch 4 adds the core infrastructure to do fraglist GRO/GSO.
> > >
> > > Patch 5 enables UDP to use fraglist GRO/GSO if configured and no
> > > GRO supported socket is found.
> >
> > Very nice feature, Steffen. Aside from questions around performance,
> > my only question is really how this relates to GSO_BY_FRAGS.
>
> They do the exact same thing AFAICT: they GSO according to a
> pre-formatted list of fragments/packets, and not to a specific size
> (such as MSS).
>
> >
> > More specifically, whether we can remove that in favor of using your
> > new skb_segment_list. That would actually be a big first step in
> > simplifying skb_segment back to something manageable.
>
> The main issue (that I know) on obsoleting GSO_BY_FRAGS is that
> dealing with frags instead of frag_list was considered easier to be
> offloaded, if ever attempted.  So this would be a step back on that
> aspect.  Other than this, it should be doable.

But GSO_BY_FRAGS also uses frag_list, not frags?

And list_skb->len for mss.

^ permalink raw reply

* RE: [PATCH] [v2] net: stmmac: selftest: avoid large stack usage
From: Jose Abreu @ 2019-09-19 13:01 UTC (permalink / raw)
  To: Arnd Bergmann, Giuseppe Cavallaro, Alexandre Torgue,
	David S. Miller, Maxime Coquelin
  Cc: netdev@vger.kernel.org, linux-stm32@st-md-mailman.stormreply.com,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <20190919123416.3070938-1-arnd@arndb.de>

From: Arnd Bergmann <arnd@arndb.de>
Date: Sep/19/2019, 13:33:43 (UTC+00:00)

> Putting a struct stmmac_rss object on the stack is a bad idea,
> as it exceeds the warning limit for a stack frame on 32-bit architectures:
> 
> drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c:1221:12: error: stack frame size of 1208 bytes in function '__stmmac_test_l3filt' [-Werror,-Wframe-larger-than=]
> drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c:1338:12: error: stack frame size of 1208 bytes in function '__stmmac_test_l4filt' [-Werror,-Wframe-larger-than=]
> 
> As the object is the trivial empty case, change the called function
> to accept a NULL pointer to mean the same thing and remove the
> large variable in the two callers.
> 
> Fixes: 4647e021193d ("net: stmmac: selftests: Add selftest for L3/L4 Filters")
> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
> ---
> v2: simply configure function, based on feedback from Jose

Looks good to me. Thanks for the fix :)

Acked-by: Jose Abreu <joabreu@synopsys.com>

---
Thanks,
Jose Miguel Abreu

^ permalink raw reply

* Re: [PATCH RFC v3 0/5] Support fraglist GRO/GSO
From: Marcelo Ricardo Leitner @ 2019-09-19 13:07 UTC (permalink / raw)
  To: Willem de Bruijn; +Cc: Steffen Klassert, Network Development, Paolo Abeni
In-Reply-To: <CA+FuTSf0N9uhOM3r8xvXiVj0xhx0KqL6-rV9EGhBJ=d8oGaxyg@mail.gmail.com>

On Thu, Sep 19, 2019 at 08:55:22AM -0400, Willem de Bruijn wrote:
> On Wed, Sep 18, 2019 at 12:58 PM Marcelo Ricardo Leitner
> <marcelo.leitner@gmail.com> wrote:
> >
> > On Wed, Sep 18, 2019 at 12:17:08PM -0400, Willem de Bruijn wrote:
> > > On Wed, Sep 18, 2019 at 3:25 AM Steffen Klassert
> > > <steffen.klassert@secunet.com> wrote:
> > > >
> > > > This patchset adds support to do GRO/GSO by chaining packets
> > > > of the same flow at the SKB frag_list pointer. This avoids
> > > > the overhead to merge payloads into one big packet, and
> > > > on the other end, if GSO is needed it avoids the overhead
> > > > of splitting the big packet back to the native form.
> > > >
> > > > Patch 1 Enables UDP GRO by default.
> > > >
> > > > Patch 2 adds a netdev feature flag to enable listifyed GRO,
> > > > this implements one of the configuration options discussed
> > > > at netconf 2019.
> > > >
> > > > Patch 3 adds a netdev software feature set that defaults to off
> > > > and assigns the new listifyed GRO feature flag to it.
> > > >
> > > > Patch 4 adds the core infrastructure to do fraglist GRO/GSO.
> > > >
> > > > Patch 5 enables UDP to use fraglist GRO/GSO if configured and no
> > > > GRO supported socket is found.
> > >
> > > Very nice feature, Steffen. Aside from questions around performance,
> > > my only question is really how this relates to GSO_BY_FRAGS.
> >
> > They do the exact same thing AFAICT: they GSO according to a
> > pre-formatted list of fragments/packets, and not to a specific size
> > (such as MSS).
> >
> > >
> > > More specifically, whether we can remove that in favor of using your
> > > new skb_segment_list. That would actually be a big first step in
> > > simplifying skb_segment back to something manageable.
> >
> > The main issue (that I know) on obsoleting GSO_BY_FRAGS is that
> > dealing with frags instead of frag_list was considered easier to be
> > offloaded, if ever attempted.  So this would be a step back on that
> > aspect.  Other than this, it should be doable.
> 
> But GSO_BY_FRAGS also uses frag_list, not frags?

/me is scratching his head.
My bad. I thought it was already using frags. Thanks.

> 
> And list_skb->len for mss.

Which stands more for 'current frag size', yes.
(list_skb, not head_skb)

^ permalink raw reply

* Re: [RFC v4 0/3] vhost: introduce mdev based hardware backend
From: Jason Wang @ 2019-09-19 13:08 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: Tiwei Bie, alex.williamson, maxime.coquelin, linux-kernel, kvm,
	virtualization, netdev, dan.daly, cunming.liang, zhihong.wang,
	lingshan.zhu
In-Reply-To: <20190918102923-mutt-send-email-mst@kernel.org>


On 2019/9/18 下午10:32, Michael S. Tsirkin wrote:
>>>> So I have some questions:
>>>>
>>>> 1) Compared to method 2, what's the advantage of creating a new vhost char
>>>> device? I guess it's for keep the API compatibility?
>>> One benefit is that we can avoid doing vhost ioctls on
>>> VFIO device fd.
>> Yes, but any benefit from doing this?
> It does seem a bit more modular, but it's certainly not a big deal.


Ok, if we go this way, it could be as simple as provide some callback to 
vhost, then vhost can just forward the ioctl through parent_ops.


>
>>>> 2) For method 2, is there any easy way for user/admin to distinguish e.g
>>>> ordinary vfio-mdev for vhost from ordinary vfio-mdev?
>>> I think device-api could be a choice.
>> Ok.
>>
>>
>>>> I saw you introduce
>>>> ops matching helper but it's not friendly to management.
>>> The ops matching helper is just to check whether a given
>>> vfio-device is based on a mdev device.
>>>
>>>> 3) A drawback of 1) and 2) is that it must follow vfio_device_ops that
>>>> assumes the parameter comes from userspace, it prevents support kernel
>>>> virtio drivers.
>>>>
>>>> 4) So comes the idea of method 3, since it register a new vhost-mdev driver,
>>>> we can use device specific ops instead of VFIO ones, then we can have a
>>>> common API between vDPA parent and vhost-mdev/virtio-mdev drivers.
>>> As the above draft shows, this requires introducing a new
>>> VFIO device driver. I think Alex's opinion matters here.


Just to clarify, a new type of mdev driver but provides dummy 
vfio_device_ops for VFIO to make container DMA ioctl work.

Thanks


>> Yes, it is.
>>
>> Thanks
>>
>>

^ permalink raw reply

* Re: [PATCH RFC v3 0/5] Support fraglist GRO/GSO
From: Marcelo Ricardo Leitner @ 2019-09-19 13:11 UTC (permalink / raw)
  To: Steffen Klassert; +Cc: Willem de Bruijn, Network Development, Paolo Abeni
In-Reply-To: <20190919094106.GM2879@gauss3.secunet.de>

On Thu, Sep 19, 2019 at 11:41:06AM +0200, Steffen Klassert wrote:
> On Wed, Sep 18, 2019 at 12:17:08PM -0400, Willem de Bruijn wrote:
> > On Wed, Sep 18, 2019 at 3:25 AM Steffen Klassert
> > <steffen.klassert@secunet.com> wrote:
> > >
> > > This patchset adds support to do GRO/GSO by chaining packets
> > > of the same flow at the SKB frag_list pointer. This avoids
> > > the overhead to merge payloads into one big packet, and
> > > on the other end, if GSO is needed it avoids the overhead
> > > of splitting the big packet back to the native form.
> > >
> > > Patch 1 Enables UDP GRO by default.
> > >
> > > Patch 2 adds a netdev feature flag to enable listifyed GRO,
> > > this implements one of the configuration options discussed
> > > at netconf 2019.
> > >
> > > Patch 3 adds a netdev software feature set that defaults to off
> > > and assigns the new listifyed GRO feature flag to it.
> > >
> > > Patch 4 adds the core infrastructure to do fraglist GRO/GSO.
> > >
> > > Patch 5 enables UDP to use fraglist GRO/GSO if configured and no
> > > GRO supported socket is found.
> > 
> > Very nice feature, Steffen.
> 
> Thanks!
> 
> > Aside from questions around performance,
> > my only question is really how this relates to GSO_BY_FRAGS.
> > 
> > More specifically, whether we can remove that in favor of using your
> > new skb_segment_list. That would actually be a big first step in
> > simplifying skb_segment back to something manageable.
> 
> As Marcelo pointed out, this should be doable.

And easier than I had thought :)
But please let me know if you need anything on it. This approach is
much cleaner.

^ permalink raw reply

* Re: dsa traffic priorization
From: Jan Lübbe @ 2019-09-19 13:21 UTC (permalink / raw)
  To: Florian Fainelli, Vladimir Oltean, Sascha Hauer
  Cc: netdev, Vivien Didelot, kernel, Andrew Lunn
In-Reply-To: <1b80f9ed-7a62-99c4-10bc-bc1887f80867@gmail.com>

Hi,

On Wed, 2019-09-18 at 10:41 -0700, Florian Fainelli wrote:
> > Technically, configuring a match-all rxnfc rule with ethtool would
> > count as 'default priority' - I have proposed that before. Now I'm not
> > entirely sure how intuitive it is, but I'm also interested in being
> > able to configure this.
> 
> That does not sound too crazy from my perspective.

Sascha and myself aren't that familiar with that part of ethtool.
You're talking about using ethtool --config-nfc/--config-ntuple on the
(external) sw1p1, sw1p2 ports? Something like this (completely untested
from the manpage):
ethtool --config-nfc sw1p1 flow-type ether queue 2 # high prio queue for ethercat
ethtool --config-nfc sw1p2 flow-type ether queue 1 # normal for rest

Currently, there seems to be no "match-all" option.

Alternatives to "queue X" might be "action" or "context", but I don't
know enough about the details to prefer one above the other.

Regards,
Jan


^ permalink raw reply

* Re: [PATCH RFC v3 0/5] Support fraglist GRO/GSO
From: Willem de Bruijn @ 2019-09-19 13:25 UTC (permalink / raw)
  To: Marcelo Ricardo Leitner
  Cc: Willem de Bruijn, Steffen Klassert, Network Development,
	Paolo Abeni
In-Reply-To: <20190919130746.GC3431@localhost.localdomain>

On Thu, Sep 19, 2019 at 9:07 AM Marcelo Ricardo Leitner
<marcelo.leitner@gmail.com> wrote:
>
> On Thu, Sep 19, 2019 at 08:55:22AM -0400, Willem de Bruijn wrote:
> > On Wed, Sep 18, 2019 at 12:58 PM Marcelo Ricardo Leitner
> > <marcelo.leitner@gmail.com> wrote:
> > >
> > > On Wed, Sep 18, 2019 at 12:17:08PM -0400, Willem de Bruijn wrote:
> > > > On Wed, Sep 18, 2019 at 3:25 AM Steffen Klassert
> > > > <steffen.klassert@secunet.com> wrote:
> > > > >
> > > > > This patchset adds support to do GRO/GSO by chaining packets
> > > > > of the same flow at the SKB frag_list pointer. This avoids
> > > > > the overhead to merge payloads into one big packet, and
> > > > > on the other end, if GSO is needed it avoids the overhead
> > > > > of splitting the big packet back to the native form.
> > > > >
> > > > > Patch 1 Enables UDP GRO by default.
> > > > >
> > > > > Patch 2 adds a netdev feature flag to enable listifyed GRO,
> > > > > this implements one of the configuration options discussed
> > > > > at netconf 2019.
> > > > >
> > > > > Patch 3 adds a netdev software feature set that defaults to off
> > > > > and assigns the new listifyed GRO feature flag to it.
> > > > >
> > > > > Patch 4 adds the core infrastructure to do fraglist GRO/GSO.
> > > > >
> > > > > Patch 5 enables UDP to use fraglist GRO/GSO if configured and no
> > > > > GRO supported socket is found.
> > > >
> > > > Very nice feature, Steffen. Aside from questions around performance,
> > > > my only question is really how this relates to GSO_BY_FRAGS.
> > >
> > > They do the exact same thing AFAICT: they GSO according to a
> > > pre-formatted list of fragments/packets, and not to a specific size
> > > (such as MSS).
> > >
> > > >
> > > > More specifically, whether we can remove that in favor of using your
> > > > new skb_segment_list. That would actually be a big first step in
> > > > simplifying skb_segment back to something manageable.
> > >
> > > The main issue (that I know) on obsoleting GSO_BY_FRAGS is that
> > > dealing with frags instead of frag_list was considered easier to be
> > > offloaded, if ever attempted.  So this would be a step back on that
> > > aspect.  Other than this, it should be doable.
> >
> > But GSO_BY_FRAGS also uses frag_list, not frags?
>
> /me is scratching his head.
> My bad. I thought it was already using frags. Thanks.
>
> >
> > And list_skb->len for mss.
>
> Which stands more for 'current frag size', yes.
> (list_skb, not head_skb)

Great. I thought I missed something :)

Frags might be cheaper from an allocation point. If at some point
going down that road.

But in the meantime, it looks like we can handle these too with
skb_segment_list, then (not necessarily in the initial patch set).

^ permalink raw reply

* Re: dsa traffic priorization
From: Vladimir Oltean @ 2019-09-19 13:33 UTC (permalink / raw)
  To: Jan Lübbe
  Cc: Florian Fainelli, Sascha Hauer, netdev, Vivien Didelot, kernel,
	Andrew Lunn
In-Reply-To: <06d2ca7441c899b4da8475f82dc706351edd0976.camel@pengutronix.de>

Hi Jan,

On Thu, 19 Sep 2019 at 16:21, Jan Lübbe <jlu@pengutronix.de> wrote:
>
> Hi,
>
> On Wed, 2019-09-18 at 10:41 -0700, Florian Fainelli wrote:
> > > Technically, configuring a match-all rxnfc rule with ethtool would
> > > count as 'default priority' - I have proposed that before. Now I'm not
> > > entirely sure how intuitive it is, but I'm also interested in being
> > > able to configure this.
> >
> > That does not sound too crazy from my perspective.
>
> Sascha and myself aren't that familiar with that part of ethtool.
> You're talking about using ethtool --config-nfc/--config-ntuple on the
> (external) sw1p1, sw1p2 ports? Something like this (completely untested
> from the manpage):
> ethtool --config-nfc sw1p1 flow-type ether queue 2 # high prio queue for ethercat
> ethtool --config-nfc sw1p2 flow-type ether queue 1 # normal for rest
>

Yes, something like that.

> Currently, there seems to be no "match-all" option.
>

Well, some keys for flow steering can be masked. See:

           src xx:yy:zz:aa:bb:cc [m xx:yy:zz:aa:bb:cc]
                  Includes the source MAC address, specified as 6
bytes in hexadecimal separated by colons, along with an optional mask.
Valid only for flow-type ether.

           dst xx:yy:zz:aa:bb:cc [m xx:yy:zz:aa:bb:cc]
                  Includes the destination MAC address, specified as 6
bytes in hexadecimal separated by colons, along with an optional mask.
Valid only for flow-type ether.

           proto N [m N]
                  Includes the Ethernet protocol number (ethertype)
and an optional mask.  Valid only for flow-type ether.

The idea is that any rule with e.g. src 00:00:00:00:00:00 and m
00:00:00:00:00:00 is an implicit match-all, because any (SMAC & m) ==
src.
The issue I see (and why I said it's not intuitive) is that there is
more than 1 way to express the same thing, and that it raises sanity
questions about rule ordering (if the rule is first, should all
subsequent flow steering rules be ignored?). Also, the driver would
have to open-code the "matchall" condition in order to detect it and
configure the default qpri.

It appears that there is a way to do this with tc-flower (or any other
classifier) as well, by specifying any null key with a mask of zero.

I don't know enough either to understand what is preferable.

> Alternatives to "queue X" might be "action" or "context", but I don't
> know enough about the details to prefer one above the other.
>
> Regards,
> Jan
>

Thanks,
-Vladimir

^ permalink raw reply

* Re: dsa traffic priorization
From: Andrew Lunn @ 2019-09-19 13:34 UTC (permalink / raw)
  To: Sascha Hauer
  Cc: Vladimir Oltean, netdev, Vivien Didelot, Florian Fainelli, kernel
In-Reply-To: <20190919080051.mr3cszpyypwqjwu4@pengutronix.de>

On Thu, Sep 19, 2019 at 10:00:51AM +0200, Sascha Hauer wrote:
> Hi Vladimir,
> 
> On Wed, Sep 18, 2019 at 05:36:08PM +0300, Vladimir Oltean wrote:
> > Hi Sascha,
> > 
> > On Wed, 18 Sep 2019 at 17:03, Sascha Hauer <s.hauer@pengutronix.de> wrote:
> > >
> > > Hi All,
> > >
> > > We have a customer using a Marvell 88e6240 switch with Ethercat on one port and
> > > regular network traffic on another port. The customer wants to configure two things
> > > on the switch: First Ethercat traffic shall be priorized over other network traffic
> > > (effectively prioritizing traffic based on port). Second the ethernet controller
> > > in the CPU is not able to handle full bandwidth traffic, so the traffic to the CPU
> > > port shall be rate limited.
> > >
> > 
> > You probably already know this, but egress shaping will not drop
> > frames, just let them accumulate in the egress queue until something
> > else happens (e.g. queue occupancy threshold triggers pause frames, or
> > tail dropping is enabled, etc). Is this what you want?
> 
> If I understand correctly then the switch has multiple output queues per
> port.

There are 4 egress queues per port.

> The Ethercat traffic will go to a higher priority queue and on
> congestion on other queues, frames designated for that queue will be
> dropped.

On ingress, there are various ways to add a priority to a frame, which
then determines which egress queue it lands in. This can be static,
all frames which ingress port X have priority Y. It can look at the
802.1p header and map the l2 priority to an queue priority. It can
look at the IPv4 TOS/DSCP value and map it to a queue priority. And
you can also use the TCAM, when it matches on a frame, it can specify
the frame priority.

If the egress queue, as selected by the queue priority is full, the
frame will be dropped.

> I just talked to our customer and he verified that their
> Ethercat traffic still goes through even when the ports with the general
> traffic are jammed with packets. So yes, I think this is what I want.

Taking frames out of the port egress queues and passing them out the
interface is then determined by the scheduler. There are two different
configurations which can be used

1) Strict priority. Frames are taken from the highest priority queue,
until it is empty. If the highest priority queue is empty, frames are
taken from the second highest priority queue. And if the second
priority is empty, the third priority queue is used, etc. This can
lead to starvation, when the lower priority queue never get serviced
because the higher priority queue have sufficient frames to keep the
line busy.

2) Weighted round robin. It takes up to 8 frames from the highest
priority queue, then up to 4 frames from the second highest priority
queue, then up to 2 frames from the 3rd highest priority queue, and
then 1 frame from the lower priority frame. And then it starts the
cycle again. This scheduler ensure there is no starvation of the
queues, but you do loose more high priority frames when congested.

> The bottleneck here is in the CPU interface. The SoC simply can't handle
> all frames coming into a fully occupied link, so we indeed have to limit
> the number of packets coming into the SoC which speaks for egress rate
> limiting. We could of course limit the ingress packets on the other
> ports, but that would mean we have to rate limit each port to the total
> desired rate divided by the number of ports to be safe, not very
> optimal.

Pause frames then start playing a role, maybe instead of, or in
combination with, egress traffic shaping.

If the SoC is overloaded, it can send a pause frame. The Switch will
then pause the scheduler. No packets are sent to the SoC, until it
unpauses. What i don't know is if the switch itself will send out
pause frames downstream, when egress queues are getting full. That
could have bad effects on overall network bandwidth, for frames which
are not destined for the SoC.

If you use egress shaping, you have to make a guess at how many bits
per second the SoC can handle, and configure the egress shaping to
that. The scheduler will then take frames from the queues at that
rate. At least for the older generation of switches, egress shaping
was rather course grained at the higher speed. Something like 500Mbps,
256Mbps, 128Mbps, 64Mbps, etc. So it might not do what you want. And
older generation switches, ingress shaping is very course grained,
mostly designed to limit broadcast storms.

There are lots of options here, how you solve your problem. But you
cannot look at just the SoC and switch combination. You need to look
at your whole network design. If you are using 802.1p, who is setting
the priority bits? If you are using TOS/DSCP, who is setting those
bits? Is there policing going on in the network edge to ensure other
traffic in the network is using the lower priority settings? Do you
really want Ethercat to take the highest priority? I would put it at
second priority, and make my SSH and SNMP client/server use the
highest priority, so when it all goes wrong, i can login and take a
look around to see what happened.

Once you have a network design, and know what you want the SoC/switch
combination to do, we can then figure out the correct API to configure
this. It might be TC with offloads, it might be ethtool with offloads,
etc.

	Andrew

^ permalink raw reply

* Re: dsa traffic priorization
From: Jan Lübbe @ 2019-09-19 13:35 UTC (permalink / raw)
  To: Florian Fainelli, Vladimir Oltean, Sascha Hauer
  Cc: netdev, Vivien Didelot, kernel, Andrew Lunn
In-Reply-To: <1b80f9ed-7a62-99c4-10bc-bc1887f80867@gmail.com>

On Wed, 2019-09-18 at 10:41 -0700, Florian Fainelli wrote:
> > > The other part of the problem seems to be that the CPU port has no network device
> > > representation in Linux, so there's no interface to configure the egress limits via tc.
> > > This has been discussed before, but it seems there hasn't been any consensous regarding how
> > > we want to proceed?
> 
> You have the DSA master network device which is on the other side of the
> switch,

We thought it might be intutive to allow configuration of this via tc
on the DSA master device (eth0, the i.MX FEC in our case). You'd
specify ingress policing via tc, and the kernel would try to hw-offload 
that to the switch's CPU egress side first, maybe fall back to offload
on the SoC's network controler and finally use the normal SW
implementation.

If that sounds reasonable, the next question would be how to express
matching on switchdev information (such as the ingress port or vlan
priority).

Regards,
Jan


^ permalink raw reply

* Re: [PATCH net-next] tcp: force a PSH flag on TSO packets
From: Eric Dumazet @ 2019-09-19 13:46 UTC (permalink / raw)
  To: Or Gerlitz, Eric Dumazet
  Cc: David S . Miller, netdev, Eric Dumazet, Soheil Hassas Yeganeh,
	Neal Cardwell, Yuchung Cheng, Daniel Borkmann, Tariq Toukan
In-Reply-To: <CAJ3xEMhh=Ow-fZqnPtxUyZsCN89dRmy=NcaO+iK+iZZYBdZbqA@mail.gmail.com>



On 9/19/19 5:17 AM, Or Gerlitz wrote:
> On Wed, Sep 11, 2019 at 12:54 AM Eric Dumazet <edumazet@google.com> wrote:
>> When tcp sends a TSO packet, adding a PSH flag on it
>> reduces the sojourn time of GRO packet in GRO receivers.
>>
>> This is particularly the case under pressure, since RX queues
>> receive packets for many concurrent flows.
>>
>> A sender can give a hint to GRO engines when it is
>> appropriate to flush a super-packet, especially when pacing
> 
> Hi Eric,
> 
> Is this correct that we add here the push flag for the tcp header template
> from which all the tcp headers for SW GSO packets will be generated?
> 
> Wouldn't that cause a too early flush on GRO engines at the receiver side?


If a TSO engine is buggy enough to add the PSH on all the segments, it needs
to be fixed urgently :)


^ permalink raw reply

* Re: [PATCH RFC v3 0/5] Support fraglist GRO/GSO
From: Paolo Abeni @ 2019-09-19 13:51 UTC (permalink / raw)
  To: Or Gerlitz, Steffen Klassert, Edward Cree, Willem de Bruijn
  Cc: Linux Netdev List
In-Reply-To: <CAJ3xEMjucmc-6k=kvEp99uZbCA50tUwPMK1z__wAG+ah7qNzsg@mail.gmail.com>

On Thu, 2019-09-19 at 15:37 +0300, Or Gerlitz wrote:
> On Wed, Sep 18, 2019 at 2:48 PM Steffen Klassert
> <steffen.klassert@secunet.com> wrote:
> > This patchset adds support to do GRO/GSO by chaining packets
> > of the same flow at the SKB frag_list pointer. This avoids
> > the overhead to merge payloads into one big packet, and
> > on the other end, if GSO is needed it avoids the overhead
> > of splitting the big packet back to the native form.
> > 
> > Patch 1 Enables UDP GRO by default.
> > 
> > Patch 2 adds a netdev feature flag to enable listifyed GRO,
> > this implements one of the configuration options discussed
> > at netconf 2019.
> [..]
> 
> The slide say that linked packets travel together though the stack.
> 
> This sounds somehow similar to the approach suggested by Ed
> for skb lists. I wonder what we can say on cases where each of the
> approaches would function better.

The 'listification' by Ed Cree can potentially aggregate across
multiple flows, so it can trigger when UDP GRO can not.

On the other side, fraglist performance impact is more limited, because
we still have to walk the list on each step. UDP GRO will improve
performances considerably more, if it can be triggered - e.g. all pkts
belong to the same flow.

Cheers,

Paolo


^ permalink raw reply

* [PATCH v2 00/20] Add support for Silicon Labs WiFi chip WF200 and further
From: Jerome Pouiller @ 2019-09-19 13:52 UTC (permalink / raw)
  To: devel@driverdev.osuosl.org, linux-wireless@vger.kernel.org
  Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	Greg Kroah-Hartman, Kalle Valo, David S . Miller, David Le Goff,
	Jerome Pouiller

From: Jérôme Pouiller <jerome.pouiller@silabs.com>

Hello all,

This series add support for Silicon Labs WiFi chip WF200 and further:

   https://www.silabs.com/documents/public/data-sheets/wf200-datasheet.pdf

This driver is an export from:

   https://github.com/SiliconLabs/wfx-linux-driver/
   
I squashed all commits from github (it definitely does not make sense to
import history). Then I split it in comprehensible (at least try to be)
commits. I hope it will help readers to understand driver architecture.
IMHO, firsts commits are clean enough to be reviewed. Things get more
difficult when I introduce mac8011 API. I tried to extract important
parts like Rx/Tx process but, big and complex patches seem unavoidable
in this part.

Architecture itself is described in commit messages.

The series below is aligned on version 2.3.1 on github. If compare this
series with github, you will find traditional differences between
external and a in-tree driver: Documentation, build infrastructure,
compatibility with older kernel revisions, etc... In add, I dropped all
code in CONFIG_WFX_SECURE_LINK. Indeed, "Secure Link" feature depends
on mbedtls and I don't think to pull mbedtls in kernel is an option
(see "TODO" file in first commit).


Changes in v2:
  - Add TODO file (and dropped todo list from cover letter)
  - Drop code relative to compatibility with older kernels
  
Jérôme Pouiller (20):
  staging: wfx: add infrastructure for new driver
  staging: wfx: add support for I/O access
  staging: wfx: add I/O API
  staging: wfx: add tracepoints for I/O access
  staging: wfx: load firmware
  staging: wfx: import HIF API headers
  staging: wfx: add IRQ handling
  staging: wfx: add tracepoints for HIF
  staging: wfx: add support for start-up indication
  staging: wfx: instantiate mac80211 data
  staging: wfx: allow to send commands to chip
  staging: wfx: add HIF commands helpers
  staging: wfx: introduce "secure link"
  staging: wfx: setup initial chip configuration
  staging: wfx: add debug files and trace debug events
  staging: wfx: allow to send 802.11 frames
  staging: wfx: allow to receive 802.11 frames
  staging: wfx: allow to scan networks
  staging: wfx: implement 802.11 key handling
  staging: wfx: implement the rest of mac80211 API

 MAINTAINERS                                   |    5 +
 drivers/staging/Kconfig                       |    2 +
 drivers/staging/Makefile                      |    1 +
 .../bindings/net/wireless/siliabs,wfx.txt     |   97 +
 drivers/staging/wfx/Kconfig                   |    7 +
 drivers/staging/wfx/Makefile                  |   24 +
 drivers/staging/wfx/TODO                      |   20 +
 drivers/staging/wfx/bh.c                      |  316 ++++
 drivers/staging/wfx/bh.h                      |   32 +
 drivers/staging/wfx/bus.h                     |   34 +
 drivers/staging/wfx/bus_sdio.c                |  268 +++
 drivers/staging/wfx/bus_spi.c                 |  264 +++
 drivers/staging/wfx/data_rx.c                 |  208 +++
 drivers/staging/wfx/data_rx.h                 |   18 +
 drivers/staging/wfx/data_tx.c                 |  799 ++++++++
 drivers/staging/wfx/data_tx.h                 |   93 +
 drivers/staging/wfx/debug.c                   |  305 +++
 drivers/staging/wfx/debug.h                   |   19 +
 drivers/staging/wfx/fwio.c                    |  387 ++++
 drivers/staging/wfx/fwio.h                    |   15 +
 drivers/staging/wfx/hif_api_cmd.h             |  681 +++++++
 drivers/staging/wfx/hif_api_general.h         |  437 +++++
 drivers/staging/wfx/hif_api_mib.h             |  558 ++++++
 drivers/staging/wfx/hif_rx.c                  |  336 ++++
 drivers/staging/wfx/hif_rx.h                  |   18 +
 drivers/staging/wfx/hif_tx.c                  |  470 +++++
 drivers/staging/wfx/hif_tx.h                  |   67 +
 drivers/staging/wfx/hif_tx_mib.h              |  281 +++
 drivers/staging/wfx/hwio.c                    |  338 ++++
 drivers/staging/wfx/hwio.h                    |   75 +
 drivers/staging/wfx/key.c                     |  258 +++
 drivers/staging/wfx/key.h                     |   22 +
 drivers/staging/wfx/main.c                    |  500 +++++
 drivers/staging/wfx/main.h                    |   48 +
 drivers/staging/wfx/queue.c                   |  606 ++++++
 drivers/staging/wfx/queue.h                   |   59 +
 drivers/staging/wfx/scan.c                    |  289 +++
 drivers/staging/wfx/scan.h                    |   42 +
 drivers/staging/wfx/secure_link.h             |   46 +
 drivers/staging/wfx/sta.c                     | 1643 +++++++++++++++++
 drivers/staging/wfx/sta.h                     |  101 +
 drivers/staging/wfx/traces.h                  |  434 +++++
 drivers/staging/wfx/wfx.h                     |  204 ++
 drivers/staging/wfx/wfx_version.h             |    3 +
 44 files changed, 10430 insertions(+)
 create mode 100644 drivers/staging/wfx/Documentation/devicetree/bindings/net/wireless/siliabs,wfx.txt
 create mode 100644 drivers/staging/wfx/Kconfig
 create mode 100644 drivers/staging/wfx/Makefile
 create mode 100644 drivers/staging/wfx/TODO
 create mode 100644 drivers/staging/wfx/bh.c
 create mode 100644 drivers/staging/wfx/bh.h
 create mode 100644 drivers/staging/wfx/bus.h
 create mode 100644 drivers/staging/wfx/bus_sdio.c
 create mode 100644 drivers/staging/wfx/bus_spi.c
 create mode 100644 drivers/staging/wfx/data_rx.c
 create mode 100644 drivers/staging/wfx/data_rx.h
 create mode 100644 drivers/staging/wfx/data_tx.c
 create mode 100644 drivers/staging/wfx/data_tx.h
 create mode 100644 drivers/staging/wfx/debug.c
 create mode 100644 drivers/staging/wfx/debug.h
 create mode 100644 drivers/staging/wfx/fwio.c
 create mode 100644 drivers/staging/wfx/fwio.h
 create mode 100644 drivers/staging/wfx/hif_api_cmd.h
 create mode 100644 drivers/staging/wfx/hif_api_general.h
 create mode 100644 drivers/staging/wfx/hif_api_mib.h
 create mode 100644 drivers/staging/wfx/hif_rx.c
 create mode 100644 drivers/staging/wfx/hif_rx.h
 create mode 100644 drivers/staging/wfx/hif_tx.c
 create mode 100644 drivers/staging/wfx/hif_tx.h
 create mode 100644 drivers/staging/wfx/hif_tx_mib.h
 create mode 100644 drivers/staging/wfx/hwio.c
 create mode 100644 drivers/staging/wfx/hwio.h
 create mode 100644 drivers/staging/wfx/key.c
 create mode 100644 drivers/staging/wfx/key.h
 create mode 100644 drivers/staging/wfx/main.c
 create mode 100644 drivers/staging/wfx/main.h
 create mode 100644 drivers/staging/wfx/queue.c
 create mode 100644 drivers/staging/wfx/queue.h
 create mode 100644 drivers/staging/wfx/scan.c
 create mode 100644 drivers/staging/wfx/scan.h
 create mode 100644 drivers/staging/wfx/secure_link.h
 create mode 100644 drivers/staging/wfx/sta.c
 create mode 100644 drivers/staging/wfx/sta.h
 create mode 100644 drivers/staging/wfx/traces.h
 create mode 100644 drivers/staging/wfx/wfx.h
 create mode 100644 drivers/staging/wfx/wfx_version.h

-- 
2.20.1

-- 
Jérôme Pouiller
Silicon Labs

^ permalink raw reply

* [PATCH v2 01/20] staging: wfx: add infrastructure for new driver
From: Jerome Pouiller @ 2019-09-19 13:52 UTC (permalink / raw)
  To: devel@driverdev.osuosl.org, linux-wireless@vger.kernel.org
  Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	Greg Kroah-Hartman, Kalle Valo, David S . Miller, David Le Goff,
	Jerome Pouiller
In-Reply-To: <20190919135220.30663-1-Jerome.Pouiller@silabs.com>

From: Jérôme Pouiller <jerome.pouiller@silabs.com>

Instantiate build infrastructure WFx driver. This driver provides support
for Wifi chipset Silicon Labs WF200 and further:

   https://www.silabs.com/documents/public/data-sheets/wf200-datasheet.pdf

This chip support SPI and SDIO bus.

SDIO interface has two particularities:
    1. Some parameters may be useful for end user (I will talk about
       gpio_wakeup later).
    2. The SDIO VID and PID of WF200 are 0000:0001 which are too much
       generic to rely on.

So, current code checks VID/PID and looks for a node in DT (since WF200
targets embedded platforms, I don't think it is a problem to rely on
DT). DT can also be used to define to parameters for driver. Currently,
if no node is found, a warning is emitted, but it could be changed in
error.

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
 MAINTAINERS                                   |  5 +
 drivers/staging/Kconfig                       |  2 +
 drivers/staging/Makefile                      |  1 +
 .../bindings/net/wireless/siliabs,wfx.txt     | 97 +++++++++++++++++++
 drivers/staging/wfx/Kconfig                   |  7 ++
 drivers/staging/wfx/Makefile                  |  8 ++
 drivers/staging/wfx/TODO                      | 20 ++++
 drivers/staging/wfx/bus.h                     | 17 ++++
 drivers/staging/wfx/bus_sdio.c                | 70 +++++++++++++
 drivers/staging/wfx/bus_spi.c                 | 53 ++++++++++
 drivers/staging/wfx/main.c                    | 47 +++++++++
 drivers/staging/wfx/wfx_version.h             |  3 +
 12 files changed, 330 insertions(+)
 create mode 100644 drivers/staging/wfx/Documentation/devicetree/bindings/net/wireless/siliabs,wfx.txt
 create mode 100644 drivers/staging/wfx/Kconfig
 create mode 100644 drivers/staging/wfx/Makefile
 create mode 100644 drivers/staging/wfx/TODO
 create mode 100644 drivers/staging/wfx/bus.h
 create mode 100644 drivers/staging/wfx/bus_sdio.c
 create mode 100644 drivers/staging/wfx/bus_spi.c
 create mode 100644 drivers/staging/wfx/main.c
 create mode 100644 drivers/staging/wfx/wfx_version.h

diff --git a/MAINTAINERS b/MAINTAINERS
index b2326dece28e..0ad6fbde3ac9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14755,6 +14755,11 @@ S:	Maintained
 F:	drivers/input/touchscreen/silead.c
 F:	drivers/platform/x86/touchscreen_dmi.c
 
+SILICON LABS WIRELESS DRIVERS (for WFxxx series)
+M:	Jérôme Pouiller <jerome.pouiller@silabs.com>
+S:	Supported
+F:	drivers/staging/wfx/
+
 SILICON MOTION SM712 FRAME BUFFER DRIVER
 M:	Sudip Mukherjee <sudipm.mukherjee@gmail.com>
 M:	Teddy Wang <teddy.wang@siliconmotion.com>
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 6f1fa4c849a1..a490141a0e88 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -125,4 +125,6 @@ source "drivers/staging/exfat/Kconfig"
 
 source "drivers/staging/qlge/Kconfig"
 
+source "drivers/staging/wfx/Kconfig"
+
 endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index a90f9b308c8d..4cb548a0ff87 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -53,3 +53,4 @@ obj-$(CONFIG_UWB)		+= uwb/
 obj-$(CONFIG_USB_WUSB)		+= wusbcore/
 obj-$(CONFIG_EXFAT_FS)		+= exfat/
 obj-$(CONFIG_QLGE)		+= qlge/
+obj-$(CONFIG_WFX)		+= wfx/
diff --git a/drivers/staging/wfx/Documentation/devicetree/bindings/net/wireless/siliabs,wfx.txt b/drivers/staging/wfx/Documentation/devicetree/bindings/net/wireless/siliabs,wfx.txt
new file mode 100644
index 000000000000..15965c9b4180
--- /dev/null
+++ b/drivers/staging/wfx/Documentation/devicetree/bindings/net/wireless/siliabs,wfx.txt
@@ -0,0 +1,97 @@
+The WFxxx chip series can be connected via SPI or via SDIO.
+
+SPI
+---
+
+You have to declare the WFxxx chip in your device tree.
+
+Required properties:
+ - compatible: Should be "silabs,wfx-spi"
+ - reg: Chip select address of device
+ - spi-max-frequency: Maximum SPI clocking speed of device in Hz
+ - interrupts-extended: Should contain interrupt line (interrupt-parent +
+   interrupt can also been used). Trigger should be `IRQ_TYPE_EDGE_RISING`.
+
+Optional properties:
+ - reset-gpios: phandle of gpio that will be used to reset chip during probe.
+   Without this property, you may encounter issues with warm boot.
+
+Please consult Documentation/devicetree/bindings/spi/spi-bus.txt for optional
+SPI connection related properties,
+
+Example:
+
+&spi1 {
+	wfx {
+		compatible = "silabs,wfx-spi";
+		pinctrl-names = "default";
+		pinctrl-0 = <&wfx_irq &wfx_gpios>;
+		interrupts-extended = <&gpio 16 IRQ_TYPE_EDGE_RISING>;
+		wakeup-gpios = <&gpio 12 GPIO_ACTIVE_HIGH>;
+		reset-gpios = <&gpio 13 GPIO_ACTIVE_HIGH>;
+		reg = <0>;
+		spi-max-frequency = <42000000>;
+	};
+};
+
+
+SDIO
+----
+
+The driver is able to detect a WFxxx chip on SDIO bus by matching its Vendor ID
+and Product ID. However, driver will only provide limited features in this
+case. Thus declaring WFxxx chip in device tree is strongly recommended (and may
+become mandatory in the future).
+
+Required properties:
+ - compatible: Should be "silabs,wfx-sdio"
+ - reg: Should be 1
+
+In addition, it is recommended to declare a mmc-pwrseq on SDIO host above WFx.
+Without it, you may encounter issues with warm boot. mmc-pwrseq should be
+compatible with mmc-pwrseq-simple. Please consult
+Documentation/devicetree/bindings/mmc/mmc-pwrseq-simple.txt for more
+information.
+
+Example:
+
+/ {
+	wfx_pwrseq: wfx_pwrseq {
+		compatible = "mmc-pwrseq-simple";
+		pinctrl-names = "default";
+		pinctrl-0 = <&wfx_reset>;
+		reset-gpios = <&gpio 13 GPIO_ACTIVE_LOW>;
+	};
+};
+
+&mmc1 {
+	mmc-pwrseq = <&wfx_pwrseq>;
+	#address-size = <1>;
+	#size = <0>;
+
+	mmc@1 {
+		compatible = "silabs,wfx-sdio";
+		reg = <1>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&wfx_wakeup>;
+		wakeup-gpios = <&gpio 12 GPIO_ACTIVE_HIGH>;
+	};
+};
+
+Note that #address-size and #size shoud already be defined in node mmc1, but it
+is rarely the case.
+
+Common properties
+-----------------
+
+Some properties are recognized either by SPI and SDIO versions:
+ - wakeup-gpios: phandle of gpio that will be used to wake-up chip. Without
+   this property, driver will disable most of power saving features.
+ - config-file: Use an alternative file as PDS. Default is `wf200.pds`. Only
+   necessary for development/debug purpose.
+ - slk_key: String representing hexdecimal value of secure link key to use.
+   Must contains 64 hexadecimal digits. Not supported in current version.
+
+WFx driver also supports `mac-address` and `local-mac-address` as described in
+Documentation/devicetree/binding/net/ethernet.txt
+
diff --git a/drivers/staging/wfx/Kconfig b/drivers/staging/wfx/Kconfig
new file mode 100644
index 000000000000..9b8a1c7a9e90
--- /dev/null
+++ b/drivers/staging/wfx/Kconfig
@@ -0,0 +1,7 @@
+config WFX
+	tristate "Silicon Labs wireless chips WF200 and further"
+	depends on MAC80211
+	depends on (SPI || MMC)
+	help
+	  This is a driver for Silicons Labs WFxxx series (WF200 and further)
+	  chipsets. This chip can be found on SPI or SDIO buses.
diff --git a/drivers/staging/wfx/Makefile b/drivers/staging/wfx/Makefile
new file mode 100644
index 000000000000..74939a5a0a1c
--- /dev/null
+++ b/drivers/staging/wfx/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+
+wfx-y := \
+	main.o
+wfx-$(CONFIG_SPI) += bus_spi.o
+wfx-$(subst m,y,$(CONFIG_MMC)) += bus_sdio.o
+
+obj-$(CONFIG_WFX) += wfx.o
diff --git a/drivers/staging/wfx/TODO b/drivers/staging/wfx/TODO
new file mode 100644
index 000000000000..be990e8f18b1
--- /dev/null
+++ b/drivers/staging/wfx/TODO
@@ -0,0 +1,20 @@
+This is a list of things that need to be done to get this driver out of the
+staging directory.
+
+  - wfx_version.h is still there in order to ensure synchronization with github.
+    It can be dropped as soon as development is entirely in kernel
+
+  - I have to take a decision about secure link support. I can:
+      - drop completely
+      - keep it in an external patch (my preferred option)
+      - replace call to mbedtls with kernel crypto API (necessitate a
+        bunch of work)
+      - pull mbedtls in kernel (non-realistic)
+
+  - mac80211 interface does not (yet) have expected quality to be placed
+    outside of staging:
+      - Some processings are redundant with mac80211 ones
+      - Many members from wfx_dev/wfx_vif can be retrieved from mac80211
+        structures
+      - Some functions are too complex
+      - ...
diff --git a/drivers/staging/wfx/bus.h b/drivers/staging/wfx/bus.h
new file mode 100644
index 000000000000..8ce871a8a9ff
--- /dev/null
+++ b/drivers/staging/wfx/bus.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Common bus abstraction layer.
+ *
+ * Copyright (c) 2017-2018, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#ifndef WFX_BUS_H
+#define WFX_BUS_H
+
+#include <linux/mmc/sdio_func.h>
+#include <linux/spi/spi.h>
+
+extern struct sdio_driver wfx_sdio_driver;
+extern struct spi_driver wfx_spi_driver;
+
+#endif
diff --git a/drivers/staging/wfx/bus_sdio.c b/drivers/staging/wfx/bus_sdio.c
new file mode 100644
index 000000000000..4b26c994f43c
--- /dev/null
+++ b/drivers/staging/wfx/bus_sdio.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * SDIO interface.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#include <linux/module.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/card.h>
+#include <linux/of_irq.h>
+
+#include "bus.h"
+
+static const struct of_device_id wfx_sdio_of_match[];
+static int wfx_sdio_probe(struct sdio_func *func,
+			  const struct sdio_device_id *id)
+{
+	struct device_node *np = func->dev.of_node;
+
+	if (func->num != 1) {
+		dev_err(&func->dev, "SDIO function number is %d while it should always be 1 (unsupported chip?)\n", func->num);
+		return -ENODEV;
+	}
+
+	if (np) {
+		if (!of_match_node(wfx_sdio_of_match, np)) {
+			dev_warn(&func->dev, "no compatible device found in DT\n");
+			return -ENODEV;
+		}
+	} else {
+		dev_warn(&func->dev, "device is not declared in DT, features will be limited\n");
+		// FIXME: ignore VID/PID and only rely on device tree
+		// return -ENODEV;
+	}
+	return -EIO; // FIXME: not yet supported
+}
+
+static void wfx_sdio_remove(struct sdio_func *func)
+{
+}
+
+#define SDIO_VENDOR_ID_SILABS        0x0000
+#define SDIO_DEVICE_ID_SILABS_WF200  0x1000
+static const struct sdio_device_id wfx_sdio_ids[] = {
+	{ SDIO_DEVICE(SDIO_VENDOR_ID_SILABS, SDIO_DEVICE_ID_SILABS_WF200) },
+	// FIXME: ignore VID/PID and only rely on device tree
+	// { SDIO_DEVICE(SDIO_ANY_ID, SDIO_ANY_ID) },
+	{ },
+};
+MODULE_DEVICE_TABLE(sdio, wfx_sdio_ids);
+
+#ifdef CONFIG_OF
+static const struct of_device_id wfx_sdio_of_match[] = {
+	{ .compatible = "silabs,wfx-sdio" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, wfx_sdio_of_match);
+#endif
+
+struct sdio_driver wfx_sdio_driver = {
+	.name = "wfx-sdio",
+	.id_table = wfx_sdio_ids,
+	.probe = wfx_sdio_probe,
+	.remove = wfx_sdio_remove,
+	.drv = {
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(wfx_sdio_of_match),
+	}
+};
diff --git a/drivers/staging/wfx/bus_spi.c b/drivers/staging/wfx/bus_spi.c
new file mode 100644
index 000000000000..574b60f513e9
--- /dev/null
+++ b/drivers/staging/wfx/bus_spi.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * SPI interface.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2011, Sagrad Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/of.h>
+
+#include "bus.h"
+
+static int wfx_spi_probe(struct spi_device *func)
+{
+	return -EIO;
+}
+
+/* Disconnect Function to be called by SPI stack when device is disconnected */
+static int wfx_spi_disconnect(struct spi_device *func)
+{
+	return 0;
+}
+
+/*
+ * For dynamic driver binding, kernel does not use OF to match driver. It only
+ * use modalias and modalias is a copy of 'compatible' DT node with vendor
+ * stripped.
+ */
+static const struct spi_device_id wfx_spi_id[] = {
+	{ "wfx-spi", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(spi, wfx_spi_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id wfx_spi_of_match[] = {
+	{ .compatible = "silabs,wfx-spi" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, wfx_spi_of_match);
+#endif
+
+struct spi_driver wfx_spi_driver = {
+	.driver = {
+		.name = "wfx-spi",
+		.of_match_table = of_match_ptr(wfx_spi_of_match),
+	},
+	.id_table = wfx_spi_id,
+	.probe = wfx_spi_probe,
+	.remove = wfx_spi_disconnect,
+};
diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c
new file mode 100644
index 000000000000..cd69f955f531
--- /dev/null
+++ b/drivers/staging/wfx/main.c
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Device probe and register.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ * Copyright (c) 2008, Johannes Berg <johannes@sipsolutions.net>
+ * Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
+ * Copyright (c) 2007-2009, Christian Lamparter <chunkeey@web.de>
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ * Copyright (c) 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
+ */
+#include <linux/module.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/spi/spi.h>
+#include <linux/etherdevice.h>
+
+#include "bus.h"
+#include "wfx_version.h"
+
+MODULE_DESCRIPTION("Silicon Labs 802.11 Wireless LAN driver for WFx");
+MODULE_AUTHOR("Jérôme Pouiller <jerome.pouiller@silabs.com>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(WFX_LABEL);
+
+static int __init wfx_core_init(void)
+{
+	int ret = 0;
+
+	pr_info("wfx: Silicon Labs " WFX_LABEL "\n");
+
+	if (IS_ENABLED(CONFIG_SPI))
+		ret = spi_register_driver(&wfx_spi_driver);
+	if (IS_ENABLED(CONFIG_MMC) && !ret)
+		ret = sdio_register_driver(&wfx_sdio_driver);
+	return ret;
+}
+module_init(wfx_core_init);
+
+static void __exit wfx_core_exit(void)
+{
+	if (IS_ENABLED(CONFIG_MMC))
+		sdio_unregister_driver(&wfx_sdio_driver);
+	if (IS_ENABLED(CONFIG_SPI))
+		spi_unregister_driver(&wfx_spi_driver);
+}
+module_exit(wfx_core_exit);
diff --git a/drivers/staging/wfx/wfx_version.h b/drivers/staging/wfx/wfx_version.h
new file mode 100644
index 000000000000..6e7f30207c73
--- /dev/null
+++ b/drivers/staging/wfx/wfx_version.h
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT! */
+#define WFX_LABEL "2.3.1"
-- 
2.20.1

^ permalink raw reply related

* [PATCH v2 18/20] staging: wfx: allow to scan networks
From: Jerome Pouiller @ 2019-09-19 13:52 UTC (permalink / raw)
  To: devel@driverdev.osuosl.org, linux-wireless@vger.kernel.org
  Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	Greg Kroah-Hartman, Kalle Valo, David S . Miller, David Le Goff,
	Jerome Pouiller
In-Reply-To: <20190919135220.30663-1-Jerome.Pouiller@silabs.com>

From: Jérôme Pouiller <jerome.pouiller@silabs.com>

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
 drivers/staging/wfx/Makefile |   1 +
 drivers/staging/wfx/bh.c     |   2 +-
 drivers/staging/wfx/hif_rx.c |  13 ++
 drivers/staging/wfx/main.c   |   5 +
 drivers/staging/wfx/scan.c   | 249 +++++++++++++++++++++++++++++++++++
 drivers/staging/wfx/scan.h   |  42 ++++++
 drivers/staging/wfx/sta.c    |  23 +++-
 drivers/staging/wfx/sta.h    |   4 +
 drivers/staging/wfx/wfx.h    |  11 ++
 9 files changed, 348 insertions(+), 2 deletions(-)
 create mode 100644 drivers/staging/wfx/scan.c
 create mode 100644 drivers/staging/wfx/scan.h

diff --git a/drivers/staging/wfx/Makefile b/drivers/staging/wfx/Makefile
index d9e21515d08e..2b8a5fa86fac 100644
--- a/drivers/staging/wfx/Makefile
+++ b/drivers/staging/wfx/Makefile
@@ -12,6 +12,7 @@ wfx-y := \
 	queue.o \
 	data_tx.o \
 	data_rx.o \
+	scan.o \
 	sta.o \
 	main.o \
 	sta.o \
diff --git a/drivers/staging/wfx/bh.c b/drivers/staging/wfx/bh.c
index ed81c3924d98..6000c03bb658 100644
--- a/drivers/staging/wfx/bh.c
+++ b/drivers/staging/wfx/bh.c
@@ -268,7 +268,7 @@ static void bh_work(struct work_struct *work)
 
 	if (last_op_is_rx)
 		ack_sdio_data(wdev);
-	if (!wdev->hif.tx_buffers_used && !work_pending(work)) {
+	if (!wdev->hif.tx_buffers_used && !work_pending(work) && !atomic_read(&wdev->scan_in_progress)) {
 		device_release(wdev);
 		release_chip = true;
 	}
diff --git a/drivers/staging/wfx/hif_rx.c b/drivers/staging/wfx/hif_rx.c
index c07984b0535d..d386fab0a90f 100644
--- a/drivers/staging/wfx/hif_rx.c
+++ b/drivers/staging/wfx/hif_rx.c
@@ -11,6 +11,7 @@
 
 #include "hif_rx.h"
 #include "wfx.h"
+#include "scan.h"
 #include "data_rx.h"
 #include "secure_link.h"
 #include "hif_api_cmd.h"
@@ -143,6 +144,17 @@ static int hif_receive_indication(struct wfx_dev *wdev, struct hif_msg *hif, voi
 	return 0;
 }
 
+static int hif_scan_complete_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
+{
+	struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
+	struct hif_ind_scan_cmpl *body = buf;
+
+	WARN_ON(!wvif);
+	wfx_scan_complete_cb(wvif, body);
+
+	return 0;
+}
+
 static int hif_join_complete_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
 {
 	struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
@@ -230,6 +242,7 @@ static const struct {
 	{ HIF_IND_ID_STARTUP,              hif_startup_indication },
 	{ HIF_IND_ID_WAKEUP,               hif_wakeup_indication },
 	{ HIF_IND_ID_JOIN_COMPLETE,        hif_join_complete_indication },
+	{ HIF_IND_ID_SCAN_CMPL,            hif_scan_complete_indication },
 	{ HIF_IND_ID_SL_EXCHANGE_PUB_KEYS, hif_keys_indication },
 	{ HIF_IND_ID_GENERIC,              hif_generic_indication },
 	{ HIF_IND_ID_ERROR,                hif_error_indication },
diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c
index cce4e30dd94a..06220bac5b75 100644
--- a/drivers/staging/wfx/main.c
+++ b/drivers/staging/wfx/main.c
@@ -55,6 +55,7 @@ static const struct ieee80211_ops wfx_ops = {
 	.add_interface		= wfx_add_interface,
 	.remove_interface	= wfx_remove_interface,
 	.tx			= wfx_tx,
+	.hw_scan		= wfx_hw_scan,
 };
 
 bool wfx_api_older_than(struct wfx_dev *wdev, int major, int minor)
@@ -203,6 +204,8 @@ struct wfx_dev *wfx_init_common(struct device *dev,
 	hw->extra_tx_headroom = sizeof(struct hif_sl_msg_hdr) + sizeof(struct hif_msg)
 				+ sizeof(struct hif_req_tx)
 				+ 4 /* alignment */ + 8 /* TKIP IV */;
+	hw->wiphy->max_scan_ssids = 2;
+	hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN;
 
 	wdev = hw->priv;
 	wdev->hw = hw;
@@ -214,6 +217,7 @@ struct wfx_dev *wfx_init_common(struct device *dev,
 	wdev->pdata.gpio_wakeup = wfx_get_gpio(dev, gpio_wakeup, "wakeup");
 	wfx_fill_sl_key(dev, &wdev->pdata);
 
+	mutex_init(&wdev->conf_mutex);
 	mutex_init(&wdev->rx_stats_lock);
 	init_completion(&wdev->firmware_ready);
 	wfx_init_hif_cmd(&wdev->hif_cmd);
@@ -225,6 +229,7 @@ struct wfx_dev *wfx_init_common(struct device *dev,
 void wfx_free_common(struct wfx_dev *wdev)
 {
 	mutex_destroy(&wdev->rx_stats_lock);
+	mutex_destroy(&wdev->conf_mutex);
 	wfx_tx_queues_deinit(wdev);
 	ieee80211_free_hw(wdev->hw);
 }
diff --git a/drivers/staging/wfx/scan.c b/drivers/staging/wfx/scan.c
new file mode 100644
index 000000000000..207b26ebc9fd
--- /dev/null
+++ b/drivers/staging/wfx/scan.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Scan related functions.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#include <net/mac80211.h>
+
+#include "scan.h"
+#include "wfx.h"
+#include "sta.h"
+#include "hif_tx_mib.h"
+
+static void __ieee80211_scan_completed_compat(struct ieee80211_hw *hw, bool aborted)
+{
+	struct cfg80211_scan_info info = {
+		.aborted = aborted ? 1 : 0,
+	};
+
+	ieee80211_scan_completed(hw, &info);
+}
+
+static int wfx_scan_start(struct wfx_vif *wvif, struct wfx_scan_params *scan)
+{
+	int ret;
+	int tmo = 500;
+
+	tmo += scan->scan_req.num_of_channels *
+	       ((20 * (scan->scan_req.max_channel_time)) + 10);
+	atomic_set(&wvif->scan.in_progress, 1);
+	atomic_set(&wvif->wdev->scan_in_progress, 1);
+
+	schedule_delayed_work(&wvif->scan.timeout, msecs_to_jiffies(tmo));
+	ret = hif_scan(wvif, scan);
+	if (ret) {
+		wfx_scan_failed_cb(wvif);
+		atomic_set(&wvif->scan.in_progress, 0);
+		atomic_set(&wvif->wdev->scan_in_progress, 0);
+		cancel_delayed_work_sync(&wvif->scan.timeout);
+	}
+	return ret;
+}
+
+int wfx_hw_scan(struct ieee80211_hw *hw,
+		   struct ieee80211_vif *vif,
+		   struct ieee80211_scan_request *hw_req)
+{
+	struct wfx_dev *wdev = hw->priv;
+	struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+	struct cfg80211_scan_request *req = &hw_req->req;
+	struct sk_buff *skb;
+	int i, ret;
+	struct hif_mib_template_frame *p;
+
+	if (!wvif)
+		return -EINVAL;
+
+	if (req->n_ssids == 1 && !req->ssids[0].ssid_len)
+		req->n_ssids = 0;
+
+	if (req->n_ssids > HIF_API_MAX_NB_SSIDS)
+		return -EINVAL;
+
+	skb = ieee80211_probereq_get(hw, wvif->vif->addr, NULL, 0, req->ie_len);
+	if (!skb)
+		return -ENOMEM;
+
+	if (req->ie_len)
+		memcpy(skb_put(skb, req->ie_len), req->ie, req->ie_len);
+
+	mutex_lock(&wdev->conf_mutex);
+
+	p = (struct hif_mib_template_frame *)skb_push(skb, 4);
+	p->frame_type = HIF_TMPLT_PRBREQ;
+	p->frame_length = cpu_to_le16(skb->len - 4);
+	ret = hif_set_template_frame(wvif, p);
+	skb_pull(skb, 4);
+
+	if (!ret)
+		/* Host want to be the probe responder. */
+		ret = wfx_fwd_probe_req(wvif, true);
+	if (ret) {
+		mutex_unlock(&wdev->conf_mutex);
+		dev_kfree_skb(skb);
+		return ret;
+	}
+
+	wfx_tx_lock_flush(wdev);
+
+	BUG_ON(wvif->scan.req);
+	wvif->scan.req = req;
+	wvif->scan.n_ssids = 0;
+	wvif->scan.status = 0;
+	wvif->scan.begin = &req->channels[0];
+	wvif->scan.curr = wvif->scan.begin;
+	wvif->scan.end = &req->channels[req->n_channels];
+	wvif->scan.output_power = wdev->output_power;
+
+	for (i = 0; i < req->n_ssids; ++i) {
+		struct hif_ssid_def *dst = &wvif->scan.ssids[wvif->scan.n_ssids];
+
+		memcpy(&dst->ssid[0], req->ssids[i].ssid, sizeof(dst->ssid));
+		dst->ssid_length = req->ssids[i].ssid_len;
+		++wvif->scan.n_ssids;
+	}
+
+	mutex_unlock(&wdev->conf_mutex);
+
+	if (skb)
+		dev_kfree_skb(skb);
+	schedule_work(&wvif->scan.work);
+	return 0;
+}
+
+void wfx_scan_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif = container_of(work, struct wfx_vif, scan.work);
+	struct ieee80211_channel **it;
+	struct wfx_scan_params scan = {
+		.scan_req.scan_type.type = 0,    /* Foreground */
+	};
+	struct ieee80211_channel *first;
+	int i;
+
+	down(&wvif->scan.lock);
+	mutex_lock(&wvif->wdev->conf_mutex);
+
+	if (!wvif->scan.req || wvif->scan.curr == wvif->scan.end) {
+		if (wvif->scan.output_power != wvif->wdev->output_power)
+			hif_set_output_power(wvif, wvif->wdev->output_power * 10);
+
+		if (wvif->scan.status < 0)
+			dev_warn(wvif->wdev->dev, "scan failed\n");
+		else if (wvif->scan.req)
+			dev_dbg(wvif->wdev->dev, "scan completed\n");
+		else
+			dev_dbg(wvif->wdev->dev, "scan canceled\n");
+
+		wvif->scan.req = NULL;
+		wfx_tx_unlock(wvif->wdev);
+		mutex_unlock(&wvif->wdev->conf_mutex);
+		__ieee80211_scan_completed_compat(wvif->wdev->hw, wvif->scan.status ? 1 : 0);
+		up(&wvif->scan.lock);
+		return;
+	}
+	first = *wvif->scan.curr;
+
+	for (it = wvif->scan.curr + 1, i = 1;
+	     it != wvif->scan.end && i < HIF_API_MAX_NB_CHANNELS;
+	     ++it, ++i) {
+		if ((*it)->band != first->band)
+			break;
+		if (((*it)->flags ^ first->flags) &
+				IEEE80211_CHAN_NO_IR)
+			break;
+		if (!(first->flags & IEEE80211_CHAN_NO_IR) &&
+		    (*it)->max_power != first->max_power)
+			break;
+	}
+	scan.scan_req.band = first->band;
+
+	if (wvif->scan.req->no_cck)
+		scan.scan_req.max_transmit_rate = API_RATE_INDEX_G_6MBPS;
+	else
+		scan.scan_req.max_transmit_rate = API_RATE_INDEX_B_1MBPS;
+	scan.scan_req.num_of_probe_requests =
+		(first->flags & IEEE80211_CHAN_NO_IR) ? 0 : 2;
+	scan.scan_req.num_of_ssi_ds = wvif->scan.n_ssids;
+	scan.ssids = &wvif->scan.ssids[0];
+	scan.scan_req.num_of_channels = it - wvif->scan.curr;
+	scan.scan_req.probe_delay = 100;
+
+	scan.ch = kcalloc(scan.scan_req.num_of_channels, sizeof(u8), GFP_KERNEL);
+
+	if (!scan.ch) {
+		wvif->scan.status = -ENOMEM;
+		goto fail;
+	}
+	for (i = 0; i < scan.scan_req.num_of_channels; ++i)
+		scan.ch[i] = wvif->scan.curr[i]->hw_value;
+
+	if (wvif->scan.curr[0]->flags & IEEE80211_CHAN_NO_IR) {
+		scan.scan_req.min_channel_time = 50;
+		scan.scan_req.max_channel_time = 150;
+	} else {
+		scan.scan_req.min_channel_time = 10;
+		scan.scan_req.max_channel_time = 50;
+	}
+	if (!(first->flags & IEEE80211_CHAN_NO_IR) &&
+	    wvif->scan.output_power != first->max_power) {
+		wvif->scan.output_power = first->max_power;
+		hif_set_output_power(wvif, wvif->scan.output_power * 10);
+	}
+	wvif->scan.status = wfx_scan_start(wvif, &scan);
+	kfree(scan.ch);
+	if (wvif->scan.status)
+		goto fail;
+	wvif->scan.curr = it;
+	mutex_unlock(&wvif->wdev->conf_mutex);
+	return;
+
+fail:
+	wvif->scan.curr = wvif->scan.end;
+	mutex_unlock(&wvif->wdev->conf_mutex);
+	up(&wvif->scan.lock);
+	schedule_work(&wvif->scan.work);
+}
+
+static void wfx_scan_complete(struct wfx_vif *wvif)
+{
+	up(&wvif->scan.lock);
+	atomic_set(&wvif->wdev->scan_in_progress, 0);
+
+	wfx_scan_work(&wvif->scan.work);
+}
+
+void wfx_scan_failed_cb(struct wfx_vif *wvif)
+{
+	if (cancel_delayed_work_sync(&wvif->scan.timeout) > 0) {
+		wvif->scan.status = -EIO;
+		schedule_work(&wvif->scan.timeout.work);
+	}
+}
+
+void wfx_scan_complete_cb(struct wfx_vif *wvif, struct hif_ind_scan_cmpl *arg)
+{
+	if (cancel_delayed_work_sync(&wvif->scan.timeout) > 0) {
+		wvif->scan.status = 1;
+		schedule_work(&wvif->scan.timeout.work);
+	}
+}
+
+void wfx_scan_timeout(struct work_struct *work)
+{
+	struct wfx_vif *wvif = container_of(work, struct wfx_vif, scan.timeout.work);
+
+	if (atomic_xchg(&wvif->scan.in_progress, 0)) {
+		if (wvif->scan.status > 0) {
+			wvif->scan.status = 0;
+		} else if (!wvif->scan.status) {
+			dev_warn(wvif->wdev->dev, "timeout waiting for scan complete notification\n");
+			wvif->scan.status = -ETIMEDOUT;
+			wvif->scan.curr = wvif->scan.end;
+			hif_stop_scan(wvif);
+		}
+		wfx_scan_complete(wvif);
+	}
+}
diff --git a/drivers/staging/wfx/scan.h b/drivers/staging/wfx/scan.h
new file mode 100644
index 000000000000..b4ddd0771a9b
--- /dev/null
+++ b/drivers/staging/wfx/scan.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Scan related functions.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#ifndef WFX_SCAN_H
+#define WFX_SCAN_H
+
+#include <linux/semaphore.h>
+#include <linux/workqueue.h>
+#include <net/mac80211.h>
+
+#include "hif_api_cmd.h"
+
+struct wfx_dev;
+struct wfx_vif;
+
+struct wfx_scan {
+	struct semaphore lock;
+	struct work_struct work;
+	struct delayed_work timeout;
+	struct cfg80211_scan_request *req;
+	struct ieee80211_channel **begin;
+	struct ieee80211_channel **curr;
+	struct ieee80211_channel **end;
+	struct hif_ssid_def ssids[HIF_API_MAX_NB_SSIDS];
+	int output_power;
+	int n_ssids;
+	int status;
+	atomic_t in_progress;
+};
+
+int wfx_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		struct ieee80211_scan_request *req);
+void wfx_scan_work(struct work_struct *work);
+void wfx_scan_timeout(struct work_struct *work);
+void wfx_scan_complete_cb(struct wfx_vif *wvif, struct hif_ind_scan_cmpl *arg);
+void wfx_scan_failed_cb(struct wfx_vif *wvif);
+
+#endif /* WFX_SCAN_H */
diff --git a/drivers/staging/wfx/sta.c b/drivers/staging/wfx/sta.c
index 5714aba1432c..5a8140100e97 100644
--- a/drivers/staging/wfx/sta.c
+++ b/drivers/staging/wfx/sta.c
@@ -9,9 +9,18 @@
 
 #include "sta.h"
 #include "wfx.h"
+#include "scan.h"
+#include "hif_tx_mib.h"
 
 #define TXOP_UNIT 32
 
+int wfx_fwd_probe_req(struct wfx_vif *wvif, bool enable)
+{
+	wvif->fwd_probe_req = enable;
+	return hif_set_rx_filter(wvif, wvif->filter_bssid,
+				 wvif->fwd_probe_req);
+}
+
 static int wfx_set_tim_impl(struct wfx_vif *wvif, bool aid0_bit_set)
 {
 	struct sk_buff *skb;
@@ -128,6 +137,8 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 		default_edca_params[IEEE80211_AC_BK].queue_id = HIF_QUEUE_ID_BESTEFFORT;
 	}
 
+	mutex_lock(&wdev->conf_mutex);
+
 	for (i = 0; i < ARRAY_SIZE(wdev->vif); i++) {
 		if (!wdev->vif[i]) {
 			wdev->vif[i] = vif;
@@ -135,8 +146,10 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 			break;
 		}
 	}
-	if (i == ARRAY_SIZE(wdev->vif))
+	if (i == ARRAY_SIZE(wdev->vif)) {
+		mutex_unlock(&wdev->conf_mutex);
 		return -EOPNOTSUPP;
+	}
 	wvif->vif = vif;
 	wvif->wdev = wdev;
 
@@ -148,6 +161,12 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 	INIT_WORK(&wvif->mcast_start_work, wfx_mcast_start_work);
 	INIT_WORK(&wvif->mcast_stop_work, wfx_mcast_stop_work);
 	timer_setup(&wvif->mcast_timeout, wfx_mcast_timeout, 0);
+
+	sema_init(&wvif->scan.lock, 1);
+	INIT_WORK(&wvif->scan.work, wfx_scan_work);
+	INIT_DELAYED_WORK(&wvif->scan.timeout, wfx_scan_timeout);
+
+	mutex_unlock(&wdev->conf_mutex);
 	BUG_ON(ARRAY_SIZE(default_edca_params) != ARRAY_SIZE(wvif->edca.params));
 	for (i = 0; i < IEEE80211_NUM_ACS; i++)
 		memcpy(&wvif->edca.params[i], &default_edca_params[i], sizeof(default_edca_params[i]));
@@ -175,7 +194,9 @@ void wfx_stop(struct ieee80211_hw *hw)
 	struct wfx_dev *wdev = hw->priv;
 
 	wfx_tx_lock_flush(wdev);
+	mutex_lock(&wdev->conf_mutex);
 	wfx_tx_queues_clear(wdev);
+	mutex_unlock(&wdev->conf_mutex);
 	wfx_tx_unlock(wdev);
 	WARN(atomic_read(&wdev->tx_lock), "tx_lock is locked");
 }
diff --git a/drivers/staging/wfx/sta.h b/drivers/staging/wfx/sta.h
index f36d94f907c7..dd1b6b3fc2f1 100644
--- a/drivers/staging/wfx/sta.h
+++ b/drivers/staging/wfx/sta.h
@@ -12,6 +12,8 @@
 
 #include "hif_api_cmd.h"
 
+struct wfx_vif;
+
 struct wfx_edca_params {
 	/* NOTE: index is a linux queue id. */
 	struct hif_req_edca_queue_params params[IEEE80211_NUM_ACS];
@@ -29,4 +31,6 @@ void wfx_stop(struct ieee80211_hw *hw);
 int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
 void wfx_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
 
+int wfx_fwd_probe_req(struct wfx_vif *wvif, bool enable);
+
 #endif /* WFX_STA_H */
diff --git a/drivers/staging/wfx/wfx.h b/drivers/staging/wfx/wfx.h
index 11775b1e06ef..50c0d9c0e528 100644
--- a/drivers/staging/wfx/wfx.h
+++ b/drivers/staging/wfx/wfx.h
@@ -19,6 +19,7 @@
 #include "queue.h"
 #include "secure_link.h"
 #include "sta.h"
+#include "scan.h"
 #include "hif_tx.h"
 #include "hif_api_general.h"
 
@@ -39,6 +40,7 @@ struct wfx_dev {
 	struct wfx_hif		hif;
 	struct sl_context	sl;
 	int			chip_frozen;
+	struct mutex		conf_mutex;
 
 	struct wfx_hif_cmd	hif_cmd;
 	struct wfx_queue	tx_queue[4];
@@ -48,6 +50,9 @@ struct wfx_dev {
 
 	struct hif_rx_stats	rx_stats;
 	struct mutex		rx_stats_lock;
+
+	int			output_power;
+	atomic_t		scan_in_progress;
 };
 
 struct wfx_vif {
@@ -71,11 +76,17 @@ struct wfx_vif {
 
 	struct tx_policy_cache	tx_policy_cache;
 	struct work_struct	tx_policy_upload_work;
+
 	u32			sta_asleep_mask;
 	u32			pspoll_mask;
 	spinlock_t		ps_state_lock;
 
+	bool			filter_bssid;
+	bool			fwd_probe_req;
+
 	struct wfx_edca_params	edca;
+
+	struct wfx_scan		scan;
 };
 
 static inline struct wfx_vif *wdev_to_wvif(struct wfx_dev *wdev, int vif_id)
-- 
2.20.1

^ permalink raw reply related

* [PATCH v2 16/20] staging: wfx: allow to send 802.11 frames
From: Jerome Pouiller @ 2019-09-19 13:52 UTC (permalink / raw)
  To: devel@driverdev.osuosl.org, linux-wireless@vger.kernel.org
  Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	Greg Kroah-Hartman, Kalle Valo, David S . Miller, David Le Goff,
	Jerome Pouiller
In-Reply-To: <20190919135220.30663-1-Jerome.Pouiller@silabs.com>

From: Jérôme Pouiller <jerome.pouiller@silabs.com>

Three things make this task more complex than it should:
  - Chip necessitate to associate a link-id to each station. It is same
    thing than association ID but, using 8 bits only.
  - Rate policy is sent separately from Tx frames
  - Driver try to handle itself power saving of stations and multicast
    data

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
 drivers/staging/wfx/Makefile  |   3 +
 drivers/staging/wfx/bh.c      |   2 +
 drivers/staging/wfx/data_tx.c | 783 ++++++++++++++++++++++++++++++++++
 drivers/staging/wfx/data_tx.h |  93 ++++
 drivers/staging/wfx/hif_rx.c  |  37 ++
 drivers/staging/wfx/hif_tx.c  |   1 +
 drivers/staging/wfx/main.c    |   4 +
 drivers/staging/wfx/queue.c   | 526 +++++++++++++++++++++++
 drivers/staging/wfx/queue.h   |  59 +++
 drivers/staging/wfx/sta.c     | 135 ++++++
 drivers/staging/wfx/sta.h     |   8 +
 drivers/staging/wfx/traces.h  |  74 ++++
 drivers/staging/wfx/wfx.h     |  58 +++
 13 files changed, 1783 insertions(+)
 create mode 100644 drivers/staging/wfx/data_tx.c
 create mode 100644 drivers/staging/wfx/data_tx.h
 create mode 100644 drivers/staging/wfx/queue.c
 create mode 100644 drivers/staging/wfx/queue.h

diff --git a/drivers/staging/wfx/Makefile b/drivers/staging/wfx/Makefile
index e158589468a3..d5ac9fafd1f1 100644
--- a/drivers/staging/wfx/Makefile
+++ b/drivers/staging/wfx/Makefile
@@ -9,6 +9,9 @@ wfx-y := \
 	fwio.o \
 	hif_tx.o \
 	hif_rx.o \
+	queue.o \
+	data_tx.o \
+	sta.o \
 	main.o \
 	sta.o \
 	debug.o
diff --git a/drivers/staging/wfx/bh.c b/drivers/staging/wfx/bh.c
index d321fd312d55..ed81c3924d98 100644
--- a/drivers/staging/wfx/bh.c
+++ b/drivers/staging/wfx/bh.c
@@ -220,6 +220,8 @@ static int bh_work_tx(struct wfx_dev *wdev, int max_msg)
 			if (try_wait_for_completion(&wdev->hif_cmd.ready)) {
 				WARN(!mutex_is_locked(&wdev->hif_cmd.lock), "data locking error");
 				hif = wdev->hif_cmd.buf_send;
+			} else {
+				hif = wfx_tx_queues_get(wdev);
 			}
 		}
 		if (!hif)
diff --git a/drivers/staging/wfx/data_tx.c b/drivers/staging/wfx/data_tx.c
new file mode 100644
index 000000000000..217d3c270706
--- /dev/null
+++ b/drivers/staging/wfx/data_tx.c
@@ -0,0 +1,783 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Datapath implementation.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#include <net/mac80211.h>
+
+#include "data_tx.h"
+#include "wfx.h"
+#include "bh.h"
+#include "queue.h"
+#include "debug.h"
+#include "traces.h"
+#include "hif_tx_mib.h"
+
+#define WFX_INVALID_RATE_ID (0xFF)
+#define WFX_LINK_ID_GC_TIMEOUT ((unsigned long)(10 * HZ))
+
+static int wfx_get_hw_rate(struct wfx_dev *wdev, const struct ieee80211_tx_rate *rate)
+{
+	if (rate->idx < 0)
+		return -1;
+	if (rate->flags & IEEE80211_TX_RC_MCS) {
+		if (rate->idx > 7) {
+			WARN(1, "wrong rate->idx value: %d", rate->idx);
+			return -1;
+		}
+		return rate->idx + 14;
+	}
+	// WFx only support 2GHz, else band information should be retreived
+	// from ieee80211_tx_info
+	return wdev->hw->wiphy->bands[NL80211_BAND_2GHZ]->bitrates[rate->idx].hw_value;
+}
+
+/* TX policy cache implementation */
+
+static void tx_policy_build(struct wfx_vif *wvif, struct tx_policy *policy,
+			    struct ieee80211_tx_rate *rates)
+{
+	int i;
+	size_t count;
+	struct wfx_dev *wdev = wvif->wdev;
+
+	BUG_ON(rates[0].idx < 0);
+	memset(policy, 0, sizeof(*policy));
+	for (i = 1; i < IEEE80211_TX_MAX_RATES; i++)
+		if (rates[i].idx < 0)
+			break;
+	count = i;
+
+	/* HACK!!! Device has problems (at least) switching from
+	 * 54Mbps CTS to 1Mbps. This switch takes enormous amount
+	 * of time (100-200 ms), leading to valuable throughput drop.
+	 * As a workaround, additional g-rates are injected to the
+	 * policy.
+	 */
+	if (count == 2 && !(rates[0].flags & IEEE80211_TX_RC_MCS) &&
+	    rates[0].idx > 4 && rates[0].count > 2 &&
+	    rates[1].idx < 2) {
+		int mid_rate = (rates[0].idx + 4) >> 1;
+
+		/* Decrease number of retries for the initial rate */
+		rates[0].count -= 2;
+
+		if (mid_rate != 4) {
+			/* Keep fallback rate at 1Mbps. */
+			rates[3] = rates[1];
+
+			/* Inject 1 transmission on lowest g-rate */
+			rates[2].idx = 4;
+			rates[2].count = 1;
+			rates[2].flags = rates[1].flags;
+
+			/* Inject 1 transmission on mid-rate */
+			rates[1].idx = mid_rate;
+			rates[1].count = 1;
+
+			/* Fallback to 1 Mbps is a really bad thing,
+			 * so let's try to increase probability of
+			 * successful transmission on the lowest g rate
+			 * even more
+			 */
+			if (rates[0].count >= 3) {
+				--rates[0].count;
+				++rates[2].count;
+			}
+
+			/* Adjust amount of rates defined */
+			count += 2;
+		} else {
+			/* Keep fallback rate at 1Mbps. */
+			rates[2] = rates[1];
+
+			/* Inject 2 transmissions on lowest g-rate */
+			rates[1].idx = 4;
+			rates[1].count = 2;
+
+			/* Adjust amount of rates defined */
+			count += 1;
+		}
+	}
+
+	for (i = 0; i < IEEE80211_TX_MAX_RATES; ++i) {
+		int rateid;
+		uint8_t count;
+
+		if (rates[i].idx < 0)
+			break;
+		WARN_ON(rates[i].count > 15);
+		rateid = wfx_get_hw_rate(wdev, &rates[i]);
+		// Pack two values in each byte of policy->rates
+		count = rates[i].count;
+		if (rateid % 2)
+			count <<= 4;
+		policy->rates[rateid / 2] |= count;
+	}
+}
+
+static bool tx_policy_is_equal(const struct tx_policy *a, const struct tx_policy *b)
+{
+	return !memcmp(a->rates, b->rates, sizeof(a->rates));
+}
+
+static int tx_policy_find(struct tx_policy_cache *cache, struct tx_policy *wanted)
+{
+	struct tx_policy *it;
+
+	list_for_each_entry(it, &cache->used, link)
+		if (tx_policy_is_equal(wanted, it))
+			return it - cache->cache;
+	list_for_each_entry(it, &cache->free, link)
+		if (tx_policy_is_equal(wanted, it))
+			return it - cache->cache;
+	return -1;
+}
+
+static void tx_policy_use(struct tx_policy_cache *cache, struct tx_policy *entry)
+{
+	++entry->usage_count;
+	list_move(&entry->link, &cache->used);
+}
+
+static int tx_policy_release(struct tx_policy_cache *cache, struct tx_policy *entry)
+{
+	int ret = --entry->usage_count;
+
+	if (!ret)
+		list_move(&entry->link, &cache->free);
+	return ret;
+}
+
+static int tx_policy_get(struct wfx_vif *wvif, struct ieee80211_tx_rate *rates,
+			 bool *renew)
+{
+	int idx;
+	struct tx_policy_cache *cache = &wvif->tx_policy_cache;
+	struct tx_policy wanted;
+
+	tx_policy_build(wvif, &wanted, rates);
+
+	spin_lock_bh(&cache->lock);
+	if (WARN_ON_ONCE(list_empty(&cache->free))) {
+		spin_unlock_bh(&cache->lock);
+		return WFX_INVALID_RATE_ID;
+	}
+	idx = tx_policy_find(cache, &wanted);
+	if (idx >= 0) {
+		*renew = false;
+	} else {
+		struct tx_policy *entry;
+		*renew = true;
+		/* If policy is not found create a new one
+		 * using the oldest entry in "free" list
+		 */
+		entry = list_entry(cache->free.prev, struct tx_policy, link);
+		memcpy(entry->rates, wanted.rates, sizeof(entry->rates));
+		entry->uploaded = 0;
+		entry->usage_count = 0;
+		idx = entry - cache->cache;
+	}
+	tx_policy_use(cache, &cache->cache[idx]);
+	if (list_empty(&cache->free)) {
+		/* Lock TX queues. */
+		wfx_tx_queues_lock(wvif->wdev);
+	}
+	spin_unlock_bh(&cache->lock);
+	return idx;
+}
+
+static void tx_policy_put(struct wfx_vif *wvif, int idx)
+{
+	int usage, locked;
+	struct tx_policy_cache *cache = &wvif->tx_policy_cache;
+
+	spin_lock_bh(&cache->lock);
+	locked = list_empty(&cache->free);
+	usage = tx_policy_release(cache, &cache->cache[idx]);
+	if (locked && !usage) {
+		/* Unlock TX queues. */
+		wfx_tx_queues_unlock(wvif->wdev);
+	}
+	spin_unlock_bh(&cache->lock);
+}
+
+static int tx_policy_upload(struct wfx_vif *wvif)
+{
+	int i;
+	struct tx_policy_cache *cache = &wvif->tx_policy_cache;
+	struct hif_mib_set_tx_rate_retry_policy *arg =
+		kzalloc(struct_size(arg, tx_rate_retry_policy, HIF_MIB_NUM_TX_RATE_RETRY_POLICIES), GFP_KERNEL);
+	struct hif_mib_tx_rate_retry_policy *dst;
+
+	spin_lock_bh(&cache->lock);
+	/* Upload only modified entries. */
+	for (i = 0; i < HIF_MIB_NUM_TX_RATE_RETRY_POLICIES; ++i) {
+		struct tx_policy *src = &cache->cache[i];
+
+		if (!src->uploaded && memzcmp(src->rates, sizeof(src->rates))) {
+			dst = arg->tx_rate_retry_policy + arg->num_tx_rate_policies;
+
+			dst->policy_index = i;
+			dst->short_retry_count = 255;
+			dst->long_retry_count = 255;
+			dst->first_rate_sel = 1;
+			dst->terminate = 1;
+			dst->count_init = 1;
+			memcpy(&dst->rates, src->rates, sizeof(src->rates));
+			src->uploaded = 1;
+			arg->num_tx_rate_policies++;
+		}
+	}
+	spin_unlock_bh(&cache->lock);
+	hif_set_tx_rate_retry_policy(wvif, arg);
+	kfree(arg);
+	return 0;
+}
+
+static void tx_policy_upload_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif =
+		container_of(work, struct wfx_vif, tx_policy_upload_work);
+
+	tx_policy_upload(wvif);
+
+	wfx_tx_unlock(wvif->wdev);
+	wfx_tx_queues_unlock(wvif->wdev);
+}
+
+void tx_policy_init(struct wfx_vif *wvif)
+{
+	struct tx_policy_cache *cache = &wvif->tx_policy_cache;
+	int i;
+
+	memset(cache, 0, sizeof(*cache));
+
+	spin_lock_init(&cache->lock);
+	INIT_LIST_HEAD(&cache->used);
+	INIT_LIST_HEAD(&cache->free);
+	INIT_WORK(&wvif->tx_policy_upload_work, tx_policy_upload_work);
+
+	for (i = 0; i < HIF_MIB_NUM_TX_RATE_RETRY_POLICIES; ++i)
+		list_add(&cache->cache[i].link, &cache->free);
+}
+
+/* Link ID related functions */
+
+static int wfx_alloc_link_id(struct wfx_vif *wvif, const u8 *mac)
+{
+	int i, ret = 0;
+	unsigned long max_inactivity = 0;
+	unsigned long now = jiffies;
+
+	spin_lock_bh(&wvif->ps_state_lock);
+	for (i = 0; i < WFX_MAX_STA_IN_AP_MODE; ++i) {
+		if (!wvif->link_id_db[i].status) {
+			ret = i + 1;
+			break;
+		} else if (wvif->link_id_db[i].status != WFX_LINK_HARD &&
+			   !wvif->wdev->tx_queue_stats.link_map_cache[i + 1]) {
+			unsigned long inactivity =
+				now - wvif->link_id_db[i].timestamp;
+
+			if (inactivity < max_inactivity)
+				continue;
+			max_inactivity = inactivity;
+			ret = i + 1;
+		}
+	}
+
+	if (ret) {
+		struct wfx_link_entry *entry = &wvif->link_id_db[ret - 1];
+
+		entry->status = WFX_LINK_RESERVE;
+		ether_addr_copy(entry->mac, mac);
+		memset(&entry->buffered, 0, WFX_MAX_TID);
+		skb_queue_head_init(&entry->rx_queue);
+		wfx_tx_lock(wvif->wdev);
+
+		if (!schedule_work(&wvif->link_id_work))
+			wfx_tx_unlock(wvif->wdev);
+	} else {
+		dev_info(wvif->wdev->dev, "no more link-id available\n");
+	}
+	spin_unlock_bh(&wvif->ps_state_lock);
+	return ret;
+}
+
+int wfx_find_link_id(struct wfx_vif *wvif, const u8 *mac)
+{
+	int i, ret = 0;
+
+	spin_lock_bh(&wvif->ps_state_lock);
+	for (i = 0; i < WFX_MAX_STA_IN_AP_MODE; ++i) {
+		if (ether_addr_equal(mac, wvif->link_id_db[i].mac) &&
+		    wvif->link_id_db[i].status) {
+			wvif->link_id_db[i].timestamp = jiffies;
+			ret = i + 1;
+			break;
+		}
+	}
+	spin_unlock_bh(&wvif->ps_state_lock);
+	return ret;
+}
+
+static int wfx_map_link(struct wfx_vif *wvif, struct wfx_link_entry *link_entry, int sta_id)
+{
+	int ret;
+
+	ret = hif_map_link(wvif, link_entry->mac, 0, sta_id);
+
+	if (ret == 0)
+		/* Save the MAC address currently associated with the peer
+		 * for future unmap request
+		 */
+		ether_addr_copy(link_entry->old_mac, link_entry->mac);
+
+	return ret;
+}
+
+int wfx_unmap_link(struct wfx_vif *wvif, int sta_id)
+{
+	u8 *mac_addr = NULL;
+
+	if (sta_id)
+		mac_addr = wvif->link_id_db[sta_id - 1].old_mac;
+
+	return hif_map_link(wvif, mac_addr, 1, sta_id);
+}
+
+void wfx_link_id_gc_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif =
+		container_of(work, struct wfx_vif, link_id_gc_work.work);
+	unsigned long now = jiffies;
+	unsigned long next_gc = -1;
+	long ttl;
+	u32 mask;
+	int i;
+
+	wfx_tx_lock_flush(wvif->wdev);
+	spin_lock_bh(&wvif->ps_state_lock);
+	for (i = 0; i < WFX_MAX_STA_IN_AP_MODE; ++i) {
+		bool need_reset = false;
+
+		mask = BIT(i + 1);
+		if (wvif->link_id_db[i].status == WFX_LINK_RESERVE ||
+		    (wvif->link_id_db[i].status == WFX_LINK_HARD &&
+		     !(wvif->link_id_map & mask))) {
+			if (wvif->link_id_map & mask) {
+				wvif->sta_asleep_mask &= ~mask;
+				wvif->pspoll_mask &= ~mask;
+				need_reset = true;
+			}
+			wvif->link_id_map |= mask;
+			if (wvif->link_id_db[i].status != WFX_LINK_HARD)
+				wvif->link_id_db[i].status = WFX_LINK_SOFT;
+
+			spin_unlock_bh(&wvif->ps_state_lock);
+			if (need_reset)
+				wfx_unmap_link(wvif, i + 1);
+			wfx_map_link(wvif, &wvif->link_id_db[i], i + 1);
+			next_gc = min(next_gc, WFX_LINK_ID_GC_TIMEOUT);
+			spin_lock_bh(&wvif->ps_state_lock);
+		} else if (wvif->link_id_db[i].status == WFX_LINK_SOFT) {
+			ttl = wvif->link_id_db[i].timestamp - now +
+					WFX_LINK_ID_GC_TIMEOUT;
+			if (ttl <= 0) {
+				need_reset = true;
+				wvif->link_id_db[i].status = WFX_LINK_OFF;
+				wvif->link_id_map &= ~mask;
+				wvif->sta_asleep_mask &= ~mask;
+				wvif->pspoll_mask &= ~mask;
+				spin_unlock_bh(&wvif->ps_state_lock);
+				wfx_unmap_link(wvif, i + 1);
+				spin_lock_bh(&wvif->ps_state_lock);
+			} else {
+				next_gc = min_t(unsigned long, next_gc, ttl);
+			}
+		}
+		if (need_reset)
+			skb_queue_purge(&wvif->link_id_db[i].rx_queue);
+	}
+	spin_unlock_bh(&wvif->ps_state_lock);
+	if (next_gc != -1)
+		schedule_delayed_work(&wvif->link_id_gc_work, next_gc);
+	wfx_tx_unlock(wvif->wdev);
+}
+
+void wfx_link_id_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif =
+		container_of(work, struct wfx_vif, link_id_work);
+
+	wfx_tx_flush(wvif->wdev);
+	wfx_link_id_gc_work(&wvif->link_id_gc_work.work);
+	wfx_tx_unlock(wvif->wdev);
+}
+
+/* Tx implementation */
+
+static bool ieee80211_is_action_back(struct ieee80211_hdr *hdr)
+{
+	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *) hdr;
+
+	if (!ieee80211_is_action(mgmt->frame_control))
+		return false;
+	if (mgmt->u.action.category != WLAN_CATEGORY_BACK)
+		return false;
+	return true;
+}
+
+static void wfx_tx_manage_pm(struct wfx_vif *wvif, struct ieee80211_hdr *hdr,
+			     struct wfx_tx_priv *tx_priv, struct ieee80211_sta *sta)
+{
+	u32 mask = ~BIT(tx_priv->raw_link_id);
+
+	spin_lock_bh(&wvif->ps_state_lock);
+	if (ieee80211_is_auth(hdr->frame_control)) {
+		wvif->sta_asleep_mask &= mask;
+		wvif->pspoll_mask &= mask;
+	}
+
+	if (tx_priv->link_id == WFX_LINK_ID_AFTER_DTIM && !wvif->mcast_buffered) {
+		wvif->mcast_buffered = true;
+		if (wvif->sta_asleep_mask)
+			schedule_work(&wvif->mcast_start_work);
+	}
+
+	if (tx_priv->raw_link_id) {
+		wvif->link_id_db[tx_priv->raw_link_id - 1].timestamp = jiffies;
+		if (tx_priv->tid < WFX_MAX_TID)
+			wvif->link_id_db[tx_priv->raw_link_id - 1].buffered[tx_priv->tid]++;
+	}
+	spin_unlock_bh(&wvif->ps_state_lock);
+
+	if (sta)
+		ieee80211_sta_set_buffered(sta, tx_priv->tid, true);
+}
+
+static uint8_t wfx_tx_get_raw_link_id(struct wfx_vif *wvif, struct ieee80211_sta *sta, struct ieee80211_hdr *hdr)
+{
+	struct wfx_sta_priv *sta_priv = sta ? (struct wfx_sta_priv *) &sta->drv_priv : NULL;
+	const u8 *da = ieee80211_get_DA(hdr);
+	int ret;
+
+	if (sta_priv && sta_priv->link_id)
+		return sta_priv->link_id;
+	if (wvif->vif->type != NL80211_IFTYPE_AP)
+		return 0;
+	if (is_multicast_ether_addr(da))
+		return 0;
+	ret = wfx_find_link_id(wvif, da);
+	if (!ret)
+		ret = wfx_alloc_link_id(wvif, da);
+	if (!ret) {
+		dev_err(wvif->wdev->dev, "no more link-id available\n");
+		return -ENOENT;
+	}
+	return ret;
+}
+
+static void wfx_tx_fixup_rates(struct ieee80211_tx_rate *rates)
+{
+	int i;
+	bool finished;
+
+	// Firmware is not able to mix rates with differents flags
+	for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) {
+		if (rates[0].flags & IEEE80211_TX_RC_SHORT_GI)
+			rates[i].flags |= IEEE80211_TX_RC_SHORT_GI;
+		if (!(rates[0].flags & IEEE80211_TX_RC_SHORT_GI))
+			rates[i].flags &= ~IEEE80211_TX_RC_SHORT_GI;
+		if (!(rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS))
+			rates[i].flags &= ~IEEE80211_TX_RC_USE_RTS_CTS;
+	}
+
+	// Sort rates and remove duplicates
+	do {
+		finished = true;
+		for (i = 0; i < IEEE80211_TX_MAX_RATES - 1; i++) {
+			if (rates[i + 1].idx == rates[i].idx && rates[i].idx != -1) {
+				rates[i].count =  max_t(int, rates[i].count, rates[i + 1].count);
+				rates[i + 1].idx = -1;
+				rates[i + 1].count = 0;
+
+				finished = false;
+			}
+			if (rates[i + 1].idx > rates[i].idx) {
+				swap(rates[i + 1], rates[i]);
+				finished = false;
+			}
+		}
+	} while (!finished);
+	// All retries use long GI
+	for (i = 1; i < IEEE80211_TX_MAX_RATES; i++)
+		rates[i].flags &= ~IEEE80211_TX_RC_SHORT_GI;
+}
+
+static uint8_t wfx_tx_get_rate_id(struct wfx_vif *wvif, struct ieee80211_tx_info *tx_info)
+{
+	bool tx_policy_renew = false;
+	uint8_t rate_id;
+
+	rate_id = tx_policy_get(wvif, tx_info->driver_rates, &tx_policy_renew);
+	WARN(rate_id == WFX_INVALID_RATE_ID, "unable to get a valid Tx policy");
+
+	if (tx_policy_renew) {
+		/* FIXME: It's not so optimal to stop TX queues every now and
+		 * then.  Better to reimplement task scheduling with a counter.
+		 */
+		wfx_tx_lock(wvif->wdev);
+		wfx_tx_queues_lock(wvif->wdev);
+		if (!schedule_work(&wvif->tx_policy_upload_work)) {
+			wfx_tx_queues_unlock(wvif->wdev);
+			wfx_tx_unlock(wvif->wdev);
+		}
+	}
+	return rate_id;
+}
+
+static struct hif_ht_tx_parameters wfx_tx_get_tx_parms(struct wfx_dev *wdev, struct ieee80211_tx_info *tx_info)
+{
+	struct ieee80211_tx_rate *rate = &tx_info->driver_rates[0];
+	struct hif_ht_tx_parameters ret = { };
+
+	if (!(rate->flags & IEEE80211_TX_RC_MCS))
+		ret.frame_format = HIF_FRAME_FORMAT_NON_HT;
+	else if (!(rate->flags & IEEE80211_TX_RC_GREEN_FIELD))
+		ret.frame_format = HIF_FRAME_FORMAT_MIXED_FORMAT_HT;
+	else
+		ret.frame_format = HIF_FRAME_FORMAT_GF_HT_11N;
+	if (rate->flags & IEEE80211_TX_RC_SHORT_GI)
+		ret.short_gi = 1;
+	if (tx_info->flags & IEEE80211_TX_CTL_STBC)
+		ret.stbc = 0; // FIXME: Not yet supported by firmware?
+	return ret;
+}
+
+static uint8_t wfx_tx_get_tid(struct ieee80211_hdr *hdr)
+{
+	// FIXME: ieee80211_get_tid(hdr) should be sufficient for all cases.
+	if (!ieee80211_is_data(hdr->frame_control))
+		return WFX_MAX_TID;
+	if (ieee80211_is_data_qos(hdr->frame_control))
+		return ieee80211_get_tid(hdr);
+	else
+		return 0;
+}
+
+static int wfx_tx_get_icv_len(struct ieee80211_key_conf *hw_key)
+{
+	int mic_space;
+
+	if (!hw_key)
+		return 0;
+	mic_space = (hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) ? 8 : 0;
+	return hw_key->icv_len + mic_space;
+}
+
+static int wfx_tx_inner(struct wfx_vif *wvif, struct ieee80211_sta *sta, struct sk_buff *skb)
+{
+	struct hif_msg *hif_msg;
+	struct hif_req_tx *req;
+	struct wfx_tx_priv *tx_priv;
+	struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
+	struct ieee80211_key_conf *hw_key = tx_info->control.hw_key;
+	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
+	int queue_id = tx_info->hw_queue;
+	size_t offset = (size_t) skb->data & 3;
+	int wmsg_len = sizeof(struct hif_msg) + sizeof(struct hif_req_tx) + offset;
+
+	WARN(queue_id >= IEEE80211_NUM_ACS, "unsupported queue_id");
+	wfx_tx_fixup_rates(tx_info->driver_rates);
+
+	// From now tx_info->control is unusable
+	memset(tx_info->rate_driver_data, 0, sizeof(struct wfx_tx_priv));
+	// Fill tx_priv
+	tx_priv = (struct wfx_tx_priv *) tx_info->rate_driver_data;
+	tx_priv->tid = wfx_tx_get_tid(hdr);
+	tx_priv->raw_link_id = wfx_tx_get_raw_link_id(wvif, sta, hdr);
+	tx_priv->link_id = tx_priv->raw_link_id;
+	if (ieee80211_has_protected(hdr->frame_control))
+		tx_priv->hw_key = hw_key;
+	if (tx_info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM)
+		tx_priv->link_id = WFX_LINK_ID_AFTER_DTIM;
+	if (sta && (sta->uapsd_queues & BIT(queue_id)))
+		tx_priv->link_id = WFX_LINK_ID_UAPSD;
+
+	// Fill hif_msg
+	WARN(skb_headroom(skb) < wmsg_len, "not enough space in skb");
+	WARN(offset & 1, "attempt to transmit an unaligned frame");
+	skb_put(skb, wfx_tx_get_icv_len(tx_priv->hw_key));
+	skb_push(skb, wmsg_len);
+	memset(skb->data, 0, wmsg_len);
+	hif_msg = (struct hif_msg *) skb->data;
+	hif_msg->len = cpu_to_le16(skb->len);
+	hif_msg->id = cpu_to_le16(HIF_REQ_ID_TX);
+	hif_msg->interface = wvif->id;
+	if (skb->len > wvif->wdev->hw_caps.size_inp_ch_buf) {
+		dev_warn(wvif->wdev->dev, "requested frame size (%d) is larger than maximum supported (%d)\n",
+			 skb->len, wvif->wdev->hw_caps.size_inp_ch_buf);
+		skb_pull(skb, wmsg_len);
+		return -EIO;
+	}
+
+	// Fill tx request
+	req = (struct hif_req_tx *) hif_msg->body;
+	req->packet_id = queue_id << 16 | IEEE80211_SEQ_TO_SN(le16_to_cpu(hdr->seq_ctrl));
+	req->data_flags.fc_offset = offset;
+	req->queue_id.peer_sta_id = tx_priv->raw_link_id;
+	// Queue index are inverted between firmware and Linux
+	req->queue_id.queue_id = 3 - queue_id;
+	req->ht_tx_parameters = wfx_tx_get_tx_parms(wvif->wdev, tx_info);
+	req->tx_flags.retry_policy_index = wfx_tx_get_rate_id(wvif, tx_info);
+
+	// Auxilliary operations
+	wfx_tx_manage_pm(wvif, hdr, tx_priv, sta);
+	wfx_tx_queue_put(wvif->wdev, &wvif->wdev->tx_queue[queue_id], skb);
+	wfx_bh_request_tx(wvif->wdev);
+	return 0;
+}
+
+void wfx_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control,
+	    struct sk_buff *skb)
+{
+	struct wfx_dev *wdev = hw->priv;
+	struct wfx_vif *wvif;
+	struct ieee80211_sta *sta = control ? control->sta : NULL;
+	struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
+	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
+	size_t driver_data_room = FIELD_SIZEOF(struct ieee80211_tx_info, rate_driver_data);
+
+	compiletime_assert(sizeof(struct wfx_tx_priv) <= driver_data_room,
+			   "struct tx_priv is too large");
+	WARN(skb->next || skb->prev, "skb is already member of a list");
+	// control.vif can be NULL for injected frames
+	if (tx_info->control.vif)
+		wvif = (struct wfx_vif *) tx_info->control.vif->drv_priv;
+	else
+		wvif = wvif_iterate(wdev, NULL);
+	if (WARN_ON(!wvif))
+		goto drop;
+	// FIXME: why?
+	if (ieee80211_is_action_back(hdr)) {
+		dev_info(wdev->dev, "drop BA action\n");
+		goto drop;
+	}
+	if (wfx_tx_inner(wvif, sta, skb))
+		goto drop;
+
+	return;
+
+drop:
+	ieee80211_tx_status_irqsafe(wdev->hw, skb);
+}
+
+void wfx_tx_confirm_cb(struct wfx_vif *wvif, struct hif_cnf_tx *arg)
+{
+	int i;
+	int tx_count;
+	struct sk_buff *skb;
+	struct ieee80211_tx_rate *rate;
+	struct ieee80211_tx_info *tx_info;
+	const struct wfx_tx_priv *tx_priv;
+
+	skb = wfx_pending_get(wvif->wdev, arg->packet_id);
+	if (!skb) {
+		dev_warn(wvif->wdev->dev, "received unknown packet_id (%#.8x) from chip\n", arg->packet_id);
+		return;
+	}
+	tx_info = IEEE80211_SKB_CB(skb);
+	tx_priv = wfx_skb_tx_priv(skb);
+	_trace_tx_stats(arg, skb, wfx_pending_get_pkt_us_delay(wvif->wdev, skb));
+
+	// You can touch to tx_priv, but don't touch to tx_info->status.
+	tx_count = arg->ack_failures;
+	if (!arg->status || arg->ack_failures)
+		tx_count += 1; // Also report success
+	for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) {
+		rate = &tx_info->status.rates[i];
+		if (rate->idx < 0)
+			break;
+		if (tx_count < rate->count && arg->status && arg->ack_failures)
+			dev_dbg(wvif->wdev->dev, "all retries were not consumed: %d != %d\n",
+				rate->count, tx_count);
+		if (tx_count <= rate->count && tx_count && arg->txed_rate != wfx_get_hw_rate(wvif->wdev, rate))
+			dev_dbg(wvif->wdev->dev, "inconsistent tx_info rates: %d != %d\n",
+				arg->txed_rate, wfx_get_hw_rate(wvif->wdev, rate));
+		if (tx_count > rate->count) {
+			tx_count -= rate->count;
+		} else if (!tx_count) {
+			rate->count = 0;
+			rate->idx = -1;
+		} else {
+			rate->count = tx_count;
+			tx_count = 0;
+		}
+	}
+	if (tx_count)
+		dev_dbg(wvif->wdev->dev, "%d more retries than expected\n", tx_count);
+	skb_trim(skb, skb->len - wfx_tx_get_icv_len(tx_priv->hw_key));
+
+	// From now, you can touch to tx_info->status, but do not touch to
+	// tx_priv anymore
+	// FIXME: use ieee80211_tx_info_clear_status()
+	memset(tx_info->rate_driver_data, 0, sizeof(tx_info->rate_driver_data));
+	memset(tx_info->pad, 0, sizeof(tx_info->pad));
+
+	if (!arg->status) {
+		tx_info->status.tx_time = arg->media_delay - arg->tx_queue_delay;
+		if (tx_info->flags & IEEE80211_TX_CTL_NO_ACK)
+			tx_info->flags |= IEEE80211_TX_STAT_NOACK_TRANSMITTED;
+		else
+			tx_info->flags |= IEEE80211_TX_STAT_ACK;
+	} else if (arg->status == HIF_REQUEUE) {
+		WARN(!arg->tx_result_flags.requeue, "incoherent status and result_flags");
+		tx_info->flags |= IEEE80211_TX_STAT_TX_FILTERED;
+	}
+	wfx_pending_remove(wvif->wdev, skb);
+}
+
+static void wfx_notify_buffered_tx(struct wfx_vif *wvif, struct sk_buff *skb,
+				   struct hif_req_tx *req)
+{
+	struct ieee80211_sta *sta;
+	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
+	int tid = wfx_tx_get_tid(hdr);
+	int raw_link_id = req->queue_id.peer_sta_id;
+	u8 *buffered;
+
+	if (raw_link_id && tid < WFX_MAX_TID) {
+		buffered = wvif->link_id_db[raw_link_id - 1].buffered;
+
+		spin_lock_bh(&wvif->ps_state_lock);
+		WARN(!buffered[tid], "inconsistent notification");
+		buffered[tid]--;
+		spin_unlock_bh(&wvif->ps_state_lock);
+
+		if (!buffered[tid]) {
+			rcu_read_lock();
+			sta = ieee80211_find_sta(wvif->vif, hdr->addr1);
+			if (sta)
+				ieee80211_sta_set_buffered(sta, tid, false);
+			rcu_read_unlock();
+		}
+	}
+}
+
+void wfx_skb_dtor(struct wfx_dev *wdev, struct sk_buff *skb)
+{
+	struct hif_msg *hif = (struct hif_msg *) skb->data;
+	struct hif_req_tx *req = (struct hif_req_tx *) hif->body;
+	struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
+	unsigned int offset = sizeof(struct hif_req_tx) + sizeof(struct hif_msg) + req->data_flags.fc_offset;
+
+	WARN_ON(!wvif);
+	skb_pull(skb, offset);
+	wfx_notify_buffered_tx(wvif, skb, req);
+	tx_policy_put(wvif, req->tx_flags.retry_policy_index);
+	ieee80211_tx_status_irqsafe(wdev->hw, skb);
+}
diff --git a/drivers/staging/wfx/data_tx.h b/drivers/staging/wfx/data_tx.h
new file mode 100644
index 000000000000..f59a259bb744
--- /dev/null
+++ b/drivers/staging/wfx/data_tx.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Datapath implementation.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#ifndef WFX_DATA_TX_H
+#define WFX_DATA_TX_H
+
+#include <linux/list.h>
+#include <net/mac80211.h>
+
+#include "hif_api_cmd.h"
+#include "hif_api_mib.h"
+
+// FIXME: use IEEE80211_NUM_TIDS
+#define WFX_MAX_TID               8
+
+struct wfx_tx_priv;
+struct wfx_dev;
+struct wfx_vif;
+
+enum wfx_link_status {
+	WFX_LINK_OFF,
+	WFX_LINK_RESERVE,
+	WFX_LINK_SOFT,
+	WFX_LINK_HARD,
+};
+
+struct wfx_link_entry {
+	unsigned long		timestamp;
+	enum wfx_link_status	status;
+	uint8_t			mac[ETH_ALEN];
+	uint8_t			old_mac[ETH_ALEN];
+	uint8_t			buffered[WFX_MAX_TID];
+	struct sk_buff_head	rx_queue;
+};
+
+struct tx_policy {
+	struct list_head link;
+	uint8_t rates[12];
+	uint8_t usage_count;
+	uint8_t uploaded;
+};
+
+struct tx_policy_cache {
+	struct tx_policy cache[HIF_MIB_NUM_TX_RATE_RETRY_POLICIES];
+	// FIXME: use a trees and drop hash from tx_policy
+	struct list_head used;
+	struct list_head free;
+	spinlock_t lock;
+};
+
+struct wfx_tx_priv {
+	ktime_t xmit_timestamp;
+	struct ieee80211_key_conf *hw_key;
+	uint8_t link_id;
+	uint8_t raw_link_id;
+	uint8_t tid;
+} __packed;
+
+void tx_policy_init(struct wfx_vif *wvif);
+
+void wfx_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control,
+	    struct sk_buff *skb);
+void wfx_tx_confirm_cb(struct wfx_vif *wvif, struct hif_cnf_tx *arg);
+void wfx_skb_dtor(struct wfx_dev *wdev, struct sk_buff *skb);
+
+int wfx_unmap_link(struct wfx_vif *wvif, int link_id);
+void wfx_link_id_work(struct work_struct *work);
+void wfx_link_id_gc_work(struct work_struct *work);
+int wfx_find_link_id(struct wfx_vif *wvif, const u8 *mac);
+
+static inline struct wfx_tx_priv *wfx_skb_tx_priv(struct sk_buff *skb)
+{
+	struct ieee80211_tx_info *tx_info;
+
+	if (!skb)
+		return NULL;
+	tx_info = IEEE80211_SKB_CB(skb);
+	return (struct wfx_tx_priv *) tx_info->rate_driver_data;
+}
+
+static inline struct hif_req_tx *wfx_skb_txreq(struct sk_buff *skb)
+{
+	struct hif_msg *hif = (struct hif_msg *) skb->data;
+	struct hif_req_tx *req = (struct hif_req_tx *) hif->body;
+
+	return req;
+}
+
+#endif /* WFX_DATA_TX_H */
diff --git a/drivers/staging/wfx/hif_rx.c b/drivers/staging/wfx/hif_rx.c
index c93bae1b6acf..97c4c2f082fb 100644
--- a/drivers/staging/wfx/hif_rx.c
+++ b/drivers/staging/wfx/hif_rx.c
@@ -53,6 +53,39 @@ static int hif_generic_confirm(struct wfx_dev *wdev, struct hif_msg *hif, void *
 	return status;
 }
 
+static int hif_tx_confirm(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
+{
+	struct hif_cnf_tx *body = buf;
+	struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
+
+	WARN_ON(!wvif);
+	if (!wvif)
+		return -EFAULT;
+
+	wfx_tx_confirm_cb(wvif, body);
+	return 0;
+}
+
+static int hif_multi_tx_confirm(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
+{
+	struct hif_cnf_multi_transmit *body = buf;
+	struct hif_cnf_tx *buf_loc = (struct hif_cnf_tx *) &body->tx_conf_payload;
+	struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
+	int count = body->num_tx_confs;
+	int i;
+
+	WARN(count <= 0, "corrupted message");
+	WARN_ON(!wvif);
+	if (!wvif)
+		return -EFAULT;
+
+	for (i = 0; i < count; ++i) {
+		wfx_tx_confirm_cb(wvif, buf_loc);
+		buf_loc++;
+	}
+	return 0;
+}
+
 static int hif_startup_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
 {
 	struct hif_ind_startup *body = buf;
@@ -174,6 +207,10 @@ static const struct {
 	int msg_id;
 	int (*handler)(struct wfx_dev *wdev, struct hif_msg *hif, void *buf);
 } hif_handlers[] = {
+	/* Confirmations */
+	{ HIF_CNF_ID_TX,                   hif_tx_confirm },
+	{ HIF_CNF_ID_MULTI_TRANSMIT,       hif_multi_tx_confirm },
+	/* Indications */
 	{ HIF_IND_ID_STARTUP,              hif_startup_indication },
 	{ HIF_IND_ID_WAKEUP,               hif_wakeup_indication },
 	{ HIF_IND_ID_JOIN_COMPLETE,        hif_join_complete_indication },
diff --git a/drivers/staging/wfx/hif_tx.c b/drivers/staging/wfx/hif_tx.c
index f8ab871aa188..157ab177b73f 100644
--- a/drivers/staging/wfx/hif_tx.c
+++ b/drivers/staging/wfx/hif_tx.c
@@ -88,6 +88,7 @@ int wfx_cmd_send(struct wfx_dev *wdev, struct hif_msg *request, void *reply, siz
 	}
 	if (!ret) {
 		dev_err(wdev->dev, "chip did not answer\n");
+		wfx_pending_dump_old_frames(wdev, 3000);
 		wdev->chip_frozen = 1;
 		reinit_completion(&wdev->hif_cmd.done);
 		ret = -ETIMEDOUT;
diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c
index 2e71f446d4d4..cce4e30dd94a 100644
--- a/drivers/staging/wfx/main.c
+++ b/drivers/staging/wfx/main.c
@@ -28,6 +28,7 @@
 #include "bh.h"
 #include "sta.h"
 #include "debug.h"
+#include "data_tx.h"
 #include "secure_link.h"
 #include "hif_tx_mib.h"
 #include "hif_api_cmd.h"
@@ -53,6 +54,7 @@ static const struct ieee80211_ops wfx_ops = {
 	.stop			= wfx_stop,
 	.add_interface		= wfx_add_interface,
 	.remove_interface	= wfx_remove_interface,
+	.tx			= wfx_tx,
 };
 
 bool wfx_api_older_than(struct wfx_dev *wdev, int major, int minor)
@@ -215,6 +217,7 @@ struct wfx_dev *wfx_init_common(struct device *dev,
 	mutex_init(&wdev->rx_stats_lock);
 	init_completion(&wdev->firmware_ready);
 	wfx_init_hif_cmd(&wdev->hif_cmd);
+	wfx_tx_queues_init(wdev);
 
 	return wdev;
 }
@@ -222,6 +225,7 @@ struct wfx_dev *wfx_init_common(struct device *dev,
 void wfx_free_common(struct wfx_dev *wdev)
 {
 	mutex_destroy(&wdev->rx_stats_lock);
+	wfx_tx_queues_deinit(wdev);
 	ieee80211_free_hw(wdev->hw);
 }
 
diff --git a/drivers/staging/wfx/queue.c b/drivers/staging/wfx/queue.c
new file mode 100644
index 000000000000..aa438be21d37
--- /dev/null
+++ b/drivers/staging/wfx/queue.c
@@ -0,0 +1,526 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * O(1) TX queue with built-in allocator.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#include <linux/sched.h>
+#include <net/mac80211.h>
+
+#include "queue.h"
+#include "wfx.h"
+#include "sta.h"
+#include "data_tx.h"
+
+void wfx_tx_lock(struct wfx_dev *wdev)
+{
+	atomic_inc(&wdev->tx_lock);
+}
+
+void wfx_tx_unlock(struct wfx_dev *wdev)
+{
+	int tx_lock = atomic_dec_return(&wdev->tx_lock);
+
+	WARN(tx_lock < 0, "inconsistent tx_lock value");
+	if (!tx_lock)
+		wfx_bh_request_tx(wdev);
+}
+
+void wfx_tx_flush(struct wfx_dev *wdev)
+{
+	int ret;
+
+	WARN(!atomic_read(&wdev->tx_lock), "tx_lock is not locked");
+
+	// Do not wait for any reply if chip is frozen
+	if (wdev->chip_frozen)
+		return;
+
+	mutex_lock(&wdev->hif_cmd.lock);
+	ret = wait_event_timeout(wdev->hif.tx_buffers_empty,
+				 !wdev->hif.tx_buffers_used,
+				 msecs_to_jiffies(3000));
+	if (!ret) {
+		dev_warn(wdev->dev, "cannot flush tx buffers (%d still busy)\n", wdev->hif.tx_buffers_used);
+		wfx_pending_dump_old_frames(wdev, 3000);
+		// FIXME: drop pending frames here
+		wdev->chip_frozen = 1;
+	}
+	mutex_unlock(&wdev->hif_cmd.lock);
+}
+
+void wfx_tx_lock_flush(struct wfx_dev *wdev)
+{
+	wfx_tx_lock(wdev);
+	wfx_tx_flush(wdev);
+}
+
+void wfx_tx_queues_lock(struct wfx_dev *wdev)
+{
+	int i;
+	struct wfx_queue *queue;
+
+	for (i = 0; i < IEEE80211_NUM_ACS; ++i) {
+		queue = &wdev->tx_queue[i];
+		spin_lock_bh(&queue->queue.lock);
+		if (queue->tx_locked_cnt++ == 0)
+			ieee80211_stop_queue(wdev->hw, queue->queue_id);
+		spin_unlock_bh(&queue->queue.lock);
+	}
+}
+
+void wfx_tx_queues_unlock(struct wfx_dev *wdev)
+{
+	int i;
+	struct wfx_queue *queue;
+
+	for (i = 0; i < IEEE80211_NUM_ACS; ++i) {
+		queue = &wdev->tx_queue[i];
+		spin_lock_bh(&queue->queue.lock);
+		BUG_ON(!queue->tx_locked_cnt);
+		if (--queue->tx_locked_cnt == 0)
+			ieee80211_wake_queue(wdev->hw, queue->queue_id);
+		spin_unlock_bh(&queue->queue.lock);
+	}
+}
+
+/* If successful, LOCKS the TX queue! */
+void wfx_tx_queues_wait_empty_vif(struct wfx_vif *wvif)
+{
+	int i;
+	bool done;
+	struct wfx_queue *queue;
+	struct sk_buff *item;
+	struct wfx_dev *wdev = wvif->wdev;
+	struct hif_msg *hif;
+
+	if (wvif->wdev->chip_frozen) {
+		wfx_tx_lock_flush(wdev);
+		wfx_tx_queues_clear(wdev);
+		return;
+	}
+
+	do {
+		done = true;
+		wfx_tx_lock_flush(wdev);
+		for (i = 0; i < IEEE80211_NUM_ACS && done; ++i) {
+			queue = &wdev->tx_queue[i];
+			spin_lock_bh(&queue->queue.lock);
+			skb_queue_walk(&queue->queue, item) {
+				hif = (struct hif_msg *) item->data;
+				if (hif->interface == wvif->id)
+					done = false;
+			}
+			spin_unlock_bh(&queue->queue.lock);
+		}
+		if (!done) {
+			wfx_tx_unlock(wdev);
+			msleep(20);
+		}
+	} while (!done);
+}
+
+static void wfx_tx_queue_clear(struct wfx_dev *wdev, struct wfx_queue *queue, struct sk_buff_head *gc_list)
+{
+	int i;
+	struct sk_buff *item;
+	struct wfx_queue_stats *stats = &wdev->tx_queue_stats;
+
+	spin_lock_bh(&queue->queue.lock);
+	while ((item = __skb_dequeue(&queue->queue)) != NULL)
+		skb_queue_head(gc_list, item);
+	spin_lock_bh(&stats->pending.lock);
+	for (i = 0; i < ARRAY_SIZE(stats->link_map_cache); ++i) {
+		stats->link_map_cache[i] -= queue->link_map_cache[i];
+		queue->link_map_cache[i] = 0;
+	}
+	spin_unlock_bh(&stats->pending.lock);
+	spin_unlock_bh(&queue->queue.lock);
+}
+
+void wfx_tx_queues_clear(struct wfx_dev *wdev)
+{
+	int i;
+	struct sk_buff *item;
+	struct sk_buff_head gc_list;
+	struct wfx_queue_stats *stats = &wdev->tx_queue_stats;
+
+	skb_queue_head_init(&gc_list);
+	for (i = 0; i < IEEE80211_NUM_ACS; ++i)
+		wfx_tx_queue_clear(wdev, &wdev->tx_queue[i], &gc_list);
+	wake_up(&stats->wait_link_id_empty);
+	while ((item = skb_dequeue(&gc_list)) != NULL)
+		wfx_skb_dtor(wdev, item);
+}
+
+void wfx_tx_queues_init(struct wfx_dev *wdev)
+{
+	int i;
+
+	memset(&wdev->tx_queue_stats, 0, sizeof(wdev->tx_queue_stats));
+	memset(wdev->tx_queue, 0, sizeof(wdev->tx_queue));
+	skb_queue_head_init(&wdev->tx_queue_stats.pending);
+	init_waitqueue_head(&wdev->tx_queue_stats.wait_link_id_empty);
+
+	for (i = 0; i < IEEE80211_NUM_ACS; ++i) {
+		wdev->tx_queue[i].queue_id = i;
+		skb_queue_head_init(&wdev->tx_queue[i].queue);
+	}
+}
+
+void wfx_tx_queues_deinit(struct wfx_dev *wdev)
+{
+	WARN_ON(!skb_queue_empty(&wdev->tx_queue_stats.pending));
+	wfx_tx_queues_clear(wdev);
+}
+
+size_t wfx_tx_queue_get_num_queued(struct wfx_queue *queue,
+				   u32 link_id_map)
+{
+	size_t ret;
+	int i, bit;
+
+	if (!link_id_map)
+		return 0;
+
+	spin_lock_bh(&queue->queue.lock);
+	if (link_id_map == (u32)-1) {
+		ret = skb_queue_len(&queue->queue);
+	} else {
+		ret = 0;
+		for (i = 0, bit = 1; i < ARRAY_SIZE(queue->link_map_cache); ++i, bit <<= 1) {
+			if (link_id_map & bit)
+				ret += queue->link_map_cache[i];
+		}
+	}
+	spin_unlock_bh(&queue->queue.lock);
+	return ret;
+}
+
+void wfx_tx_queue_put(struct wfx_dev *wdev, struct wfx_queue *queue, struct sk_buff *skb)
+{
+	struct wfx_queue_stats *stats = &wdev->tx_queue_stats;
+	struct wfx_tx_priv *tx_priv = wfx_skb_tx_priv(skb);
+
+	WARN(tx_priv->link_id >= ARRAY_SIZE(stats->link_map_cache), "invalid link-id value");
+	spin_lock_bh(&queue->queue.lock);
+	__skb_queue_tail(&queue->queue, skb);
+
+	++queue->link_map_cache[tx_priv->link_id];
+
+	spin_lock_bh(&stats->pending.lock);
+	++stats->link_map_cache[tx_priv->link_id];
+	spin_unlock_bh(&stats->pending.lock);
+	spin_unlock_bh(&queue->queue.lock);
+}
+
+struct sk_buff *wfx_tx_queue_get(struct wfx_dev *wdev, struct wfx_queue *queue, u32 link_id_map)
+{
+	struct sk_buff *skb = NULL;
+	struct sk_buff *item;
+	struct wfx_queue_stats *stats = &wdev->tx_queue_stats;
+	struct wfx_tx_priv *tx_priv;
+	bool wakeup_stats = false;
+
+	spin_lock_bh(&queue->queue.lock);
+	skb_queue_walk(&queue->queue, item) {
+		tx_priv = wfx_skb_tx_priv(item);
+		if (link_id_map & BIT(tx_priv->link_id)) {
+			skb = item;
+			break;
+		}
+	}
+	WARN_ON(!skb);
+	if (skb) {
+		tx_priv = wfx_skb_tx_priv(skb);
+		tx_priv->xmit_timestamp = ktime_get();
+		__skb_unlink(skb, &queue->queue);
+		--queue->link_map_cache[tx_priv->link_id];
+
+		spin_lock_bh(&stats->pending.lock);
+		__skb_queue_tail(&stats->pending, skb);
+		if (!--stats->link_map_cache[tx_priv->link_id])
+			wakeup_stats = true;
+		spin_unlock_bh(&stats->pending.lock);
+	}
+	spin_unlock_bh(&queue->queue.lock);
+	if (wakeup_stats)
+		wake_up(&stats->wait_link_id_empty);
+	return skb;
+}
+
+int wfx_pending_requeue(struct wfx_dev *wdev, struct sk_buff *skb)
+{
+	struct wfx_queue_stats *stats = &wdev->tx_queue_stats;
+	struct wfx_tx_priv *tx_priv = wfx_skb_tx_priv(skb);
+	struct wfx_queue *queue = &wdev->tx_queue[skb_get_queue_mapping(skb)];
+
+	WARN_ON(skb_get_queue_mapping(skb) > 3);
+	spin_lock_bh(&queue->queue.lock);
+	++queue->link_map_cache[tx_priv->link_id];
+
+	spin_lock_bh(&stats->pending.lock);
+	++stats->link_map_cache[tx_priv->link_id];
+	__skb_unlink(skb, &stats->pending);
+	spin_unlock_bh(&stats->pending.lock);
+	__skb_queue_tail(&queue->queue, skb);
+	spin_unlock_bh(&queue->queue.lock);
+	return 0;
+}
+
+int wfx_pending_remove(struct wfx_dev *wdev, struct sk_buff *skb)
+{
+	struct wfx_queue_stats *stats = &wdev->tx_queue_stats;
+
+	spin_lock_bh(&stats->pending.lock);
+	__skb_unlink(skb, &stats->pending);
+	spin_unlock_bh(&stats->pending.lock);
+	wfx_skb_dtor(wdev, skb);
+
+	return 0;
+}
+
+struct sk_buff *wfx_pending_get(struct wfx_dev *wdev, u32 packet_id)
+{
+	struct sk_buff *skb;
+	struct hif_req_tx *req;
+	struct wfx_queue_stats *stats = &wdev->tx_queue_stats;
+
+	spin_lock_bh(&stats->pending.lock);
+	skb_queue_walk(&stats->pending, skb) {
+		req = wfx_skb_txreq(skb);
+		if (req->packet_id == packet_id) {
+			spin_unlock_bh(&stats->pending.lock);
+			return skb;
+		}
+	}
+	WARN_ON(1);
+	spin_unlock_bh(&stats->pending.lock);
+	return NULL;
+}
+
+void wfx_pending_dump_old_frames(struct wfx_dev *wdev, unsigned int limit_ms)
+{
+	struct wfx_queue_stats *stats = &wdev->tx_queue_stats;
+	ktime_t now = ktime_get();
+	struct wfx_tx_priv *tx_priv;
+	struct hif_req_tx *req;
+	struct sk_buff *skb;
+	bool first = true;
+
+	spin_lock_bh(&stats->pending.lock);
+	skb_queue_walk(&stats->pending, skb) {
+		tx_priv = wfx_skb_tx_priv(skb);
+		req = wfx_skb_txreq(skb);
+		if (ktime_after(now, ktime_add_ms(tx_priv->xmit_timestamp, limit_ms))) {
+			if (first) {
+				dev_info(wdev->dev, "frames stuck in firmware since %dms or more:\n",
+					 limit_ms);
+				first = false;
+			}
+			dev_info(wdev->dev, "   id %08x sent %lldms ago\n",
+				 req->packet_id,
+				 ktime_ms_delta(now, tx_priv->xmit_timestamp));
+		}
+	}
+	spin_unlock_bh(&stats->pending.lock);
+}
+
+unsigned int wfx_pending_get_pkt_us_delay(struct wfx_dev *wdev, struct sk_buff *skb)
+{
+	ktime_t now = ktime_get();
+	struct wfx_tx_priv *tx_priv = wfx_skb_tx_priv(skb);
+
+	return ktime_us_delta(now, tx_priv->xmit_timestamp);
+}
+
+bool wfx_tx_queues_is_empty(struct wfx_dev *wdev)
+{
+	int i;
+	struct sk_buff_head *queue;
+	bool ret = true;
+
+	for (i = 0; i < IEEE80211_NUM_ACS; i++) {
+		queue = &wdev->tx_queue[i].queue;
+		spin_lock_bh(&queue->lock);
+		if (!skb_queue_empty(queue))
+			ret = false;
+		spin_unlock_bh(&queue->lock);
+	}
+	return ret;
+}
+
+static int wfx_get_prio_queue(struct wfx_vif *wvif,
+				 u32 tx_allowed_mask, int *total)
+{
+	static const int urgent = BIT(WFX_LINK_ID_AFTER_DTIM) |
+		BIT(WFX_LINK_ID_UAPSD);
+	struct hif_req_edca_queue_params *edca;
+	unsigned int score, best = -1;
+	int winner = -1;
+	int i;
+
+	/* search for a winner using edca params */
+	for (i = 0; i < IEEE80211_NUM_ACS; ++i) {
+		int queued;
+
+		edca = &wvif->edca.params[i];
+		queued = wfx_tx_queue_get_num_queued(&wvif->wdev->tx_queue[i],
+				tx_allowed_mask);
+		if (!queued)
+			continue;
+		*total += queued;
+		score = ((edca->aifsn + edca->cw_min) << 16) +
+			((edca->cw_max - edca->cw_min) *
+			 (get_random_int() & 0xFFFF));
+		if (score < best && (winner < 0 || i != 3)) {
+			best = score;
+			winner = i;
+		}
+	}
+
+	/* override winner if bursting */
+	if (winner >= 0 && wvif->wdev->tx_burst_idx >= 0 &&
+	    winner != wvif->wdev->tx_burst_idx &&
+	    !wfx_tx_queue_get_num_queued(&wvif->wdev->tx_queue[winner], tx_allowed_mask & urgent) &&
+	    wfx_tx_queue_get_num_queued(&wvif->wdev->tx_queue[wvif->wdev->tx_burst_idx], tx_allowed_mask))
+		winner = wvif->wdev->tx_burst_idx;
+
+	return winner;
+}
+
+static int wfx_tx_queue_mask_get(struct wfx_vif *wvif,
+				     struct wfx_queue **queue_p,
+				     u32 *tx_allowed_mask_p,
+				     bool *more)
+{
+	int idx;
+	u32 tx_allowed_mask;
+	int total = 0;
+
+	/* Search for a queue with multicast frames buffered */
+	if (wvif->mcast_tx) {
+		tx_allowed_mask = BIT(WFX_LINK_ID_AFTER_DTIM);
+		idx = wfx_get_prio_queue(wvif, tx_allowed_mask, &total);
+		if (idx >= 0) {
+			*more = total > 1;
+			goto found;
+		}
+	}
+
+	/* Search for unicast traffic */
+	tx_allowed_mask = ~wvif->sta_asleep_mask;
+	tx_allowed_mask |= BIT(WFX_LINK_ID_UAPSD);
+	if (wvif->sta_asleep_mask) {
+		tx_allowed_mask |= wvif->pspoll_mask;
+		tx_allowed_mask &= ~BIT(WFX_LINK_ID_AFTER_DTIM);
+	} else {
+		tx_allowed_mask |= BIT(WFX_LINK_ID_AFTER_DTIM);
+	}
+	idx = wfx_get_prio_queue(wvif, tx_allowed_mask, &total);
+	if (idx < 0)
+		return -ENOENT;
+
+found:
+	*queue_p = &wvif->wdev->tx_queue[idx];
+	*tx_allowed_mask_p = tx_allowed_mask;
+	return 0;
+}
+
+struct hif_msg *wfx_tx_queues_get(struct wfx_dev *wdev)
+{
+	struct sk_buff *skb;
+	struct hif_msg *hif = NULL;
+	struct hif_req_tx *req = NULL;
+	struct wfx_queue *queue = NULL;
+	struct wfx_queue *vif_queue = NULL;
+	u32 tx_allowed_mask = 0;
+	u32 vif_tx_allowed_mask = 0;
+	const struct wfx_tx_priv *tx_priv = NULL;
+	struct wfx_vif *wvif;
+	/* More is used only for broadcasts. */
+	bool more = false;
+	bool vif_more = false;
+	int not_found;
+	int burst;
+
+	for (;;) {
+		int ret = -ENOENT;
+		int queue_num;
+		struct ieee80211_hdr *hdr;
+
+		if (atomic_read(&wdev->tx_lock))
+			return NULL;
+
+		wvif = NULL;
+		while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
+			spin_lock_bh(&wvif->ps_state_lock);
+
+			not_found = wfx_tx_queue_mask_get(wvif, &vif_queue, &vif_tx_allowed_mask, &vif_more);
+
+			if (wvif->mcast_buffered && (not_found || !vif_more) &&
+					(wvif->mcast_tx || !wvif->sta_asleep_mask)) {
+				wvif->mcast_buffered = false;
+				if (wvif->mcast_tx) {
+					wvif->mcast_tx = false;
+					schedule_work(&wvif->mcast_stop_work);
+				}
+			}
+
+			spin_unlock_bh(&wvif->ps_state_lock);
+
+			if (vif_more) {
+				more = 1;
+				tx_allowed_mask = vif_tx_allowed_mask;
+				queue = vif_queue;
+				ret = 0;
+				break;
+			} else if (!not_found) {
+				if (queue && queue != vif_queue)
+					dev_info(wdev->dev, "vifs disagree about queue priority\n");
+				tx_allowed_mask |= vif_tx_allowed_mask;
+				queue = vif_queue;
+				ret = 0;
+			}
+		}
+
+		if (ret)
+			return 0;
+
+		queue_num = queue - wdev->tx_queue;
+
+		skb = wfx_tx_queue_get(wdev, queue, tx_allowed_mask);
+		if (!skb)
+			continue;
+		tx_priv = wfx_skb_tx_priv(skb);
+		hif = (struct hif_msg *) skb->data;
+		wvif = wdev_to_wvif(wdev, hif->interface);
+		WARN_ON(!wvif);
+
+		wvif->pspoll_mask &= ~BIT(tx_priv->raw_link_id);
+
+		/* allow bursting if txop is set */
+		if (wvif->edca.params[queue_num].tx_op_limit)
+			burst = (int)wfx_tx_queue_get_num_queued(queue, tx_allowed_mask) + 1;
+		else
+			burst = 1;
+
+		/* store index of bursting queue */
+		if (burst > 1)
+			wdev->tx_burst_idx = queue_num;
+		else
+			wdev->tx_burst_idx = -1;
+
+		/* more buffered multicast/broadcast frames
+		 *  ==> set MoreData flag in IEEE 802.11 header
+		 *  to inform PS STAs
+		 */
+		if (more) {
+			req = (struct hif_req_tx *) hif->body;
+			hdr = (struct ieee80211_hdr *) (req->frame + req->data_flags.fc_offset);
+			hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_MOREDATA);
+		}
+		return hif;
+	}
+}
diff --git a/drivers/staging/wfx/queue.h b/drivers/staging/wfx/queue.h
new file mode 100644
index 000000000000..938dbf3469e7
--- /dev/null
+++ b/drivers/staging/wfx/queue.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * O(1) TX queue with built-in allocator.
+ *
+ * Copyright (c) 2017-2018, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#ifndef WFX_QUEUE_H
+#define WFX_QUEUE_H
+
+#include <linux/skbuff.h>
+
+#include "hif_api_cmd.h"
+
+#define WFX_MAX_STA_IN_AP_MODE    14
+#define WFX_LINK_ID_AFTER_DTIM    (WFX_MAX_STA_IN_AP_MODE + 1)
+#define WFX_LINK_ID_UAPSD         (WFX_MAX_STA_IN_AP_MODE + 2)
+#define WFX_LINK_ID_MAX           (WFX_MAX_STA_IN_AP_MODE + 3)
+
+struct wfx_dev;
+struct wfx_vif;
+
+struct wfx_queue {
+	struct sk_buff_head	queue;
+	int			tx_locked_cnt;
+	int			link_map_cache[WFX_LINK_ID_MAX];
+	u8			queue_id;
+};
+
+struct wfx_queue_stats {
+	int			link_map_cache[WFX_LINK_ID_MAX];
+	struct sk_buff_head	pending;
+	wait_queue_head_t	wait_link_id_empty;
+};
+
+void wfx_tx_lock(struct wfx_dev *wdev);
+void wfx_tx_unlock(struct wfx_dev *wdev);
+void wfx_tx_flush(struct wfx_dev *wdev);
+void wfx_tx_lock_flush(struct wfx_dev *wdev);
+
+void wfx_tx_queues_init(struct wfx_dev *wdev);
+void wfx_tx_queues_deinit(struct wfx_dev *wdev);
+void wfx_tx_queues_lock(struct wfx_dev *wdev);
+void wfx_tx_queues_unlock(struct wfx_dev *wdev);
+void wfx_tx_queues_clear(struct wfx_dev *wdev);
+bool wfx_tx_queues_is_empty(struct wfx_dev *wdev);
+void wfx_tx_queues_wait_empty_vif(struct wfx_vif *wvif);
+struct hif_msg *wfx_tx_queues_get(struct wfx_dev *wdev);
+
+void wfx_tx_queue_put(struct wfx_dev *wdev, struct wfx_queue *queue, struct sk_buff *skb);
+size_t wfx_tx_queue_get_num_queued(struct wfx_queue *queue, u32 link_id_map);
+
+struct sk_buff *wfx_pending_get(struct wfx_dev *wdev, u32 packet_id);
+int wfx_pending_remove(struct wfx_dev *wdev, struct sk_buff *skb);
+int wfx_pending_requeue(struct wfx_dev *wdev, struct sk_buff *skb);
+unsigned int wfx_pending_get_pkt_us_delay(struct wfx_dev *wdev, struct sk_buff *skb);
+void wfx_pending_dump_old_frames(struct wfx_dev *wdev, unsigned int limit_ms);
+
+#endif /* WFX_QUEUE_H */
diff --git a/drivers/staging/wfx/sta.c b/drivers/staging/wfx/sta.c
index fe3ff6536a87..5714aba1432c 100644
--- a/drivers/staging/wfx/sta.c
+++ b/drivers/staging/wfx/sta.c
@@ -10,11 +10,123 @@
 #include "sta.h"
 #include "wfx.h"
 
+#define TXOP_UNIT 32
+
+static int wfx_set_tim_impl(struct wfx_vif *wvif, bool aid0_bit_set)
+{
+	struct sk_buff *skb;
+	struct hif_ie_flags target_frame = {
+		.beacon = 1,
+	};
+	u16 tim_offset, tim_length;
+	u8 *tim_ptr;
+
+	skb = ieee80211_beacon_get_tim(wvif->wdev->hw, wvif->vif,
+				       &tim_offset, &tim_length);
+	if (!skb)
+		return -ENOENT;
+	tim_ptr = skb->data + tim_offset;
+
+	if (tim_offset && tim_length >= 6) {
+		/* Ignore DTIM count from mac80211:
+		 * firmware handles DTIM internally.
+		 */
+		tim_ptr[2] = 0;
+
+		/* Set/reset aid0 bit */
+		if (aid0_bit_set)
+			tim_ptr[4] |= 1;
+		else
+			tim_ptr[4] &= ~1;
+	}
+
+	hif_update_ie(wvif, &target_frame, tim_ptr, tim_length);
+	dev_kfree_skb(skb);
+
+	return 0;
+}
+
+static void wfx_mcast_start_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif = container_of(work, struct wfx_vif, mcast_start_work);
+
+	cancel_work_sync(&wvif->mcast_stop_work);
+	if (!wvif->aid0_bit_set) {
+		wfx_tx_lock_flush(wvif->wdev);
+		wfx_set_tim_impl(wvif, true);
+		wvif->aid0_bit_set = true;
+		mod_timer(&wvif->mcast_timeout, TU_TO_JIFFIES(1000));
+		wfx_tx_unlock(wvif->wdev);
+	}
+}
+
+static void wfx_mcast_stop_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif = container_of(work, struct wfx_vif, mcast_stop_work);
+
+	if (wvif->aid0_bit_set) {
+		del_timer_sync(&wvif->mcast_timeout);
+		wfx_tx_lock_flush(wvif->wdev);
+		wvif->aid0_bit_set = false;
+		wfx_set_tim_impl(wvif, false);
+		wfx_tx_unlock(wvif->wdev);
+	}
+}
+
+static void wfx_mcast_timeout(struct timer_list *t)
+{
+	struct wfx_vif *wvif = from_timer(wvif, t, mcast_timeout);
+
+	dev_warn(wvif->wdev->dev, "multicast delivery timeout\n");
+	spin_lock_bh(&wvif->ps_state_lock);
+	wvif->mcast_tx = wvif->aid0_bit_set && wvif->mcast_buffered;
+	if (wvif->mcast_tx)
+		wfx_bh_request_tx(wvif->wdev);
+	spin_unlock_bh(&wvif->ps_state_lock);
+}
+
 int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 {
 	int i;
 	struct wfx_dev *wdev = hw->priv;
 	struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+	// FIXME: parameters are set by kernel juste after interface_add.
+	// Keep struct hif_req_edca_queue_params blank?
+	struct hif_req_edca_queue_params default_edca_params[] = {
+		[IEEE80211_AC_VO] = {
+			.queue_id = HIF_QUEUE_ID_VOICE,
+			.aifsn = 2,
+			.cw_min = 3,
+			.cw_max = 7,
+			.tx_op_limit = TXOP_UNIT * 47,
+		},
+		[IEEE80211_AC_VI] = {
+			.queue_id = HIF_QUEUE_ID_VIDEO,
+			.aifsn = 2,
+			.cw_min = 7,
+			.cw_max = 15,
+			.tx_op_limit = TXOP_UNIT * 94,
+		},
+		[IEEE80211_AC_BE] = {
+			.queue_id = HIF_QUEUE_ID_BESTEFFORT,
+			.aifsn = 3,
+			.cw_min = 15,
+			.cw_max = 1023,
+			.tx_op_limit = TXOP_UNIT * 0,
+		},
+		[IEEE80211_AC_BK] = {
+			.queue_id = HIF_QUEUE_ID_BACKGROUND,
+			.aifsn = 7,
+			.cw_min = 15,
+			.cw_max = 1023,
+			.tx_op_limit = TXOP_UNIT * 0,
+		},
+	};
+
+	if (wfx_api_older_than(wdev, 2, 0)) {
+		default_edca_params[IEEE80211_AC_BE].queue_id = HIF_QUEUE_ID_BACKGROUND;
+		default_edca_params[IEEE80211_AC_BK].queue_id = HIF_QUEUE_ID_BESTEFFORT;
+	}
 
 	for (i = 0; i < ARRAY_SIZE(wdev->vif); i++) {
 		if (!wdev->vif[i]) {
@@ -28,12 +140,29 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 	wvif->vif = vif;
 	wvif->wdev = wdev;
 
+	INIT_WORK(&wvif->link_id_work, wfx_link_id_work);
+	INIT_DELAYED_WORK(&wvif->link_id_gc_work, wfx_link_id_gc_work);
+
+	spin_lock_init(&wvif->ps_state_lock);
+
+	INIT_WORK(&wvif->mcast_start_work, wfx_mcast_start_work);
+	INIT_WORK(&wvif->mcast_stop_work, wfx_mcast_stop_work);
+	timer_setup(&wvif->mcast_timeout, wfx_mcast_timeout, 0);
+	BUG_ON(ARRAY_SIZE(default_edca_params) != ARRAY_SIZE(wvif->edca.params));
+	for (i = 0; i < IEEE80211_NUM_ACS; i++)
+		memcpy(&wvif->edca.params[i], &default_edca_params[i], sizeof(default_edca_params[i]));
+	tx_policy_init(wvif);
 	return 0;
 }
 
 void wfx_remove_interface(struct ieee80211_hw *hw,
 			  struct ieee80211_vif *vif)
 {
+	struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+
+	wfx_tx_queues_wait_empty_vif(wvif);
+	cancel_delayed_work_sync(&wvif->link_id_gc_work);
+	del_timer_sync(&wvif->mcast_timeout);
 }
 
 int wfx_start(struct ieee80211_hw *hw)
@@ -43,4 +172,10 @@ int wfx_start(struct ieee80211_hw *hw)
 
 void wfx_stop(struct ieee80211_hw *hw)
 {
+	struct wfx_dev *wdev = hw->priv;
+
+	wfx_tx_lock_flush(wdev);
+	wfx_tx_queues_clear(wdev);
+	wfx_tx_unlock(wdev);
+	WARN(atomic_read(&wdev->tx_lock), "tx_lock is locked");
 }
diff --git a/drivers/staging/wfx/sta.h b/drivers/staging/wfx/sta.h
index f17b4d1511d7..f36d94f907c7 100644
--- a/drivers/staging/wfx/sta.h
+++ b/drivers/staging/wfx/sta.h
@@ -10,6 +10,14 @@
 
 #include <net/mac80211.h>
 
+#include "hif_api_cmd.h"
+
+struct wfx_edca_params {
+	/* NOTE: index is a linux queue id. */
+	struct hif_req_edca_queue_params params[IEEE80211_NUM_ACS];
+	bool uapsd_enable[IEEE80211_NUM_ACS];
+};
+
 struct wfx_sta_priv {
 	int link_id;
 	int vif_id;
diff --git a/drivers/staging/wfx/traces.h b/drivers/staging/wfx/traces.h
index e7b03b940535..67457cda133b 100644
--- a/drivers/staging/wfx/traces.h
+++ b/drivers/staging/wfx/traces.h
@@ -12,6 +12,7 @@
 #define _WFX_TRACE_H
 
 #include <linux/tracepoint.h>
+#include <net/mac80211.h>
 
 #include "bus.h"
 #include "hif_api_cmd.h"
@@ -349,6 +350,79 @@ TRACE_EVENT(bh_stats,
 );
 #define _trace_bh_stats(ind, req, cnf, busy, release) trace_bh_stats(ind, req, cnf, busy, release)
 
+TRACE_EVENT(tx_stats,
+	TP_PROTO(struct hif_cnf_tx *tx_cnf, struct sk_buff *skb, int delay),
+	TP_ARGS(tx_cnf, skb, delay),
+	TP_STRUCT__entry(
+		__field(int, pkt_id)
+		__field(int, delay_media)
+		__field(int, delay_queue)
+		__field(int, delay_fw)
+		__field(int, ack_failures)
+		__field(int, flags)
+		__array(int, rate, 4)
+		__array(int, tx_count, 4)
+	),
+	TP_fast_assign(
+		// Keep sync with wfx_rates definition in main.c
+		static const int hw_rate[] = { 0, 1, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13 };
+		struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
+		struct ieee80211_tx_rate *rates = tx_info->driver_rates;
+		int i;
+
+		__entry->pkt_id = tx_cnf->packet_id;
+		__entry->delay_media = tx_cnf->media_delay;
+		__entry->delay_queue = tx_cnf->tx_queue_delay;
+		__entry->delay_fw = delay;
+		__entry->ack_failures = tx_cnf->ack_failures;
+		if (!tx_cnf->status || __entry->ack_failures)
+			__entry->ack_failures += 1;
+
+		for (i = 0; i < IEEE80211_NUM_ACS; i++) {
+			if (rates[0].flags & IEEE80211_TX_RC_MCS)
+				__entry->rate[i] = rates[i].idx;
+			else
+				__entry->rate[i] = hw_rate[rates[i].idx];
+			__entry->tx_count[i] = rates[i].count;
+		}
+		__entry->flags = 0;
+		if (rates[0].flags & IEEE80211_TX_RC_MCS)
+			__entry->flags |= 0x01;
+		if (rates[0].flags & IEEE80211_TX_RC_SHORT_GI)
+			__entry->flags |= 0x02;
+		if (rates[0].flags & IEEE80211_TX_RC_GREEN_FIELD)
+			__entry->flags |= 0x04;
+		if (rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS)
+			__entry->flags |= 0x08;
+		if (tx_info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM)
+			__entry->flags |= 0x10;
+		if (tx_cnf->status)
+			__entry->flags |= 0x20;
+		if (tx_cnf->status == HIF_REQUEUE)
+			__entry->flags |= 0x40;
+	),
+	TP_printk("packet ID: %08x, rate policy: %s %d|%d %d|%d %d|%d %d|%d -> %d attempt, Delays media/queue/total: %4dus/%4dus/%4dus",
+		__entry->pkt_id,
+		__print_flags(__entry->flags, NULL,
+			{ 0x01, "M" }, { 0x02, "S" }, { 0x04, "G" },
+			{ 0x08, "R" }, { 0x10, "D" }, { 0x20, "F" },
+			{ 0x40, "Q" }),
+		__entry->rate[0],
+		__entry->tx_count[0],
+		__entry->rate[1],
+		__entry->tx_count[1],
+		__entry->rate[2],
+		__entry->tx_count[2],
+		__entry->rate[3],
+		__entry->tx_count[3],
+		__entry->ack_failures,
+		__entry->delay_media,
+		__entry->delay_queue,
+		__entry->delay_fw
+	)
+);
+#define _trace_tx_stats(tx_cnf, skb, delay) trace_tx_stats(tx_cnf, skb, delay)
+
 #endif
 
 /* This part must be outside protection */
diff --git a/drivers/staging/wfx/wfx.h b/drivers/staging/wfx/wfx.h
index 49b776a07515..11775b1e06ef 100644
--- a/drivers/staging/wfx/wfx.h
+++ b/drivers/staging/wfx/wfx.h
@@ -14,8 +14,11 @@
 #include <net/mac80211.h>
 
 #include "bh.h"
+#include "data_tx.h"
 #include "main.h"
+#include "queue.h"
 #include "secure_link.h"
+#include "sta.h"
 #include "hif_tx.h"
 #include "hif_api_general.h"
 
@@ -38,6 +41,10 @@ struct wfx_dev {
 	int			chip_frozen;
 
 	struct wfx_hif_cmd	hif_cmd;
+	struct wfx_queue	tx_queue[4];
+	struct wfx_queue_stats	tx_queue_stats;
+	int			tx_burst_idx;
+	atomic_t		tx_lock;
 
 	struct hif_rx_stats	rx_stats;
 	struct mutex		rx_stats_lock;
@@ -47,6 +54,28 @@ struct wfx_vif {
 	struct wfx_dev		*wdev;
 	struct ieee80211_vif	*vif;
 	int			id;
+
+
+	u32			link_id_map;
+	struct wfx_link_entry	link_id_db[WFX_MAX_STA_IN_AP_MODE];
+	struct delayed_work	link_id_gc_work;
+	struct work_struct	link_id_work;
+
+	bool			aid0_bit_set;
+	bool			mcast_tx;
+	bool			mcast_buffered;
+	struct timer_list	mcast_timeout;
+	struct work_struct	mcast_start_work;
+	struct work_struct	mcast_stop_work;
+
+
+	struct tx_policy_cache	tx_policy_cache;
+	struct work_struct	tx_policy_upload_work;
+	u32			sta_asleep_mask;
+	u32			pspoll_mask;
+	spinlock_t		ps_state_lock;
+
+	struct wfx_edca_params	edca;
 };
 
 static inline struct wfx_vif *wdev_to_wvif(struct wfx_dev *wdev, int vif_id)
@@ -62,4 +91,33 @@ static inline struct wfx_vif *wdev_to_wvif(struct wfx_dev *wdev, int vif_id)
 	return (struct wfx_vif *) wdev->vif[vif_id]->drv_priv;
 }
 
+static inline struct wfx_vif *wvif_iterate(struct wfx_dev *wdev, struct wfx_vif *cur)
+{
+	int i;
+	int mark = 0;
+	struct wfx_vif *tmp;
+
+	if (!cur)
+		mark = 1;
+	for (i = 0; i < ARRAY_SIZE(wdev->vif); i++) {
+		tmp = wdev_to_wvif(wdev, i);
+		if (mark && tmp)
+			return tmp;
+		if (tmp == cur)
+			mark = 1;
+	}
+	return NULL;
+}
+
+static inline int memzcmp(void *src, unsigned int size)
+{
+	uint8_t *buf = src;
+
+	if (!size)
+		return 0;
+	if (*buf)
+		return 1;
+	return memcmp(buf, buf + 1, size - 1);
+}
+
 #endif /* WFX_H */
-- 
2.20.1

^ permalink raw reply related

* [PATCH v2 19/20] staging: wfx: implement 802.11 key handling
From: Jerome Pouiller @ 2019-09-19 13:52 UTC (permalink / raw)
  To: devel@driverdev.osuosl.org, linux-wireless@vger.kernel.org
  Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	Greg Kroah-Hartman, Kalle Valo, David S . Miller, David Le Goff,
	Jerome Pouiller
In-Reply-To: <20190919135220.30663-1-Jerome.Pouiller@silabs.com>

From: Jérôme Pouiller <jerome.pouiller@silabs.com>

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
 drivers/staging/wfx/Makefile |   1 +
 drivers/staging/wfx/key.c    | 258 +++++++++++++++++++++++++++++++++++
 drivers/staging/wfx/key.h    |  22 +++
 drivers/staging/wfx/main.c   |   2 +
 drivers/staging/wfx/sta.c    |   4 +
 drivers/staging/wfx/wfx.h    |  19 +++
 6 files changed, 306 insertions(+)
 create mode 100644 drivers/staging/wfx/key.c
 create mode 100644 drivers/staging/wfx/key.h

diff --git a/drivers/staging/wfx/Makefile b/drivers/staging/wfx/Makefile
index 2b8a5fa86fac..0d9c1ed092f6 100644
--- a/drivers/staging/wfx/Makefile
+++ b/drivers/staging/wfx/Makefile
@@ -14,6 +14,7 @@ wfx-y := \
 	data_rx.o \
 	scan.o \
 	sta.o \
+	key.o \
 	main.o \
 	sta.o \
 	debug.o
diff --git a/drivers/staging/wfx/key.c b/drivers/staging/wfx/key.c
new file mode 100644
index 000000000000..4e7d2b510a9c
--- /dev/null
+++ b/drivers/staging/wfx/key.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Key management related functions.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#include <net/mac80211.h>
+
+#include "key.h"
+#include "wfx.h"
+#include "hif_tx_mib.h"
+
+static int wfx_alloc_key(struct wfx_dev *wdev)
+{
+	int idx;
+
+	idx = ffs(~wdev->key_map) - 1;
+	if (idx < 0 || idx >= MAX_KEY_ENTRIES)
+		return -1;
+
+	wdev->key_map |= BIT(idx);
+	wdev->keys[idx].entry_index = idx;
+	return idx;
+}
+
+static void wfx_free_key(struct wfx_dev *wdev, int idx)
+{
+	BUG_ON(!(wdev->key_map & BIT(idx)));
+	memset(&wdev->keys[idx], 0, sizeof(wdev->keys[idx]));
+	wdev->key_map &= ~BIT(idx);
+}
+
+static uint8_t fill_wep_pair(struct hif_wep_pairwise_key *msg,
+			     struct ieee80211_key_conf *key, u8 *peer_addr)
+{
+	WARN_ON(key->keylen > sizeof(msg->key_data));
+	msg->key_length = key->keylen;
+	memcpy(msg->key_data, key->key, key->keylen);
+	ether_addr_copy(msg->peer_address, peer_addr);
+	return HIF_KEY_TYPE_WEP_PAIRWISE;
+}
+
+static uint8_t fill_wep_group(struct hif_wep_group_key *msg,
+			      struct ieee80211_key_conf *key)
+{
+	WARN_ON(key->keylen > sizeof(msg->key_data));
+	msg->key_id = key->keyidx;
+	msg->key_length = key->keylen;
+	memcpy(msg->key_data, key->key, key->keylen);
+	return HIF_KEY_TYPE_WEP_DEFAULT;
+}
+
+static uint8_t fill_tkip_pair(struct hif_tkip_pairwise_key *msg,
+			      struct ieee80211_key_conf *key, u8 *peer_addr)
+{
+	uint8_t *keybuf = key->key;
+
+	WARN_ON(key->keylen != sizeof(msg->tkip_key_data)
+			       + sizeof(msg->tx_mic_key)
+			       + sizeof(msg->rx_mic_key));
+	memcpy(msg->tkip_key_data, keybuf, sizeof(msg->tkip_key_data));
+	keybuf += sizeof(msg->tkip_key_data);
+	memcpy(msg->tx_mic_key, keybuf, sizeof(msg->tx_mic_key));
+	keybuf += sizeof(msg->tx_mic_key);
+	memcpy(msg->rx_mic_key, keybuf, sizeof(msg->rx_mic_key));
+	ether_addr_copy(msg->peer_address, peer_addr);
+	return HIF_KEY_TYPE_TKIP_PAIRWISE;
+}
+
+static uint8_t fill_tkip_group(struct hif_tkip_group_key *msg,
+			       struct ieee80211_key_conf *key,
+			       struct ieee80211_key_seq *seq,
+			       enum nl80211_iftype iftype)
+{
+	uint8_t *keybuf = key->key;
+
+	WARN_ON(key->keylen != sizeof(msg->tkip_key_data)
+			       + 2 * sizeof(msg->rx_mic_key));
+	msg->key_id = key->keyidx;
+	memcpy(msg->rx_sequence_counter, &seq->tkip.iv16, sizeof(seq->tkip.iv16));
+	memcpy(msg->rx_sequence_counter + sizeof(uint16_t), &seq->tkip.iv32, sizeof(seq->tkip.iv32));
+	memcpy(msg->tkip_key_data, keybuf, sizeof(msg->tkip_key_data));
+	keybuf += sizeof(msg->tkip_key_data);
+	if (iftype == NL80211_IFTYPE_AP)
+		// Use Tx MIC Key
+		memcpy(msg->rx_mic_key, keybuf + 0, sizeof(msg->rx_mic_key));
+	else
+		// Use Rx MIC Key
+		memcpy(msg->rx_mic_key, keybuf + 8, sizeof(msg->rx_mic_key));
+	return HIF_KEY_TYPE_TKIP_GROUP;
+}
+
+static uint8_t fill_ccmp_pair(struct hif_aes_pairwise_key *msg,
+			      struct ieee80211_key_conf *key, u8 *peer_addr)
+{
+	WARN_ON(key->keylen != sizeof(msg->aes_key_data));
+	ether_addr_copy(msg->peer_address, peer_addr);
+	memcpy(msg->aes_key_data, key->key, key->keylen);
+	return HIF_KEY_TYPE_AES_PAIRWISE;
+}
+
+static uint8_t fill_ccmp_group(struct hif_aes_group_key *msg,
+			       struct ieee80211_key_conf *key,
+			       struct ieee80211_key_seq *seq)
+{
+	WARN_ON(key->keylen != sizeof(msg->aes_key_data));
+	memcpy(msg->aes_key_data, key->key, key->keylen);
+	memcpy(msg->rx_sequence_counter, seq->ccmp.pn, sizeof(seq->ccmp.pn));
+	memreverse(msg->rx_sequence_counter, sizeof(seq->ccmp.pn));
+	msg->key_id = key->keyidx;
+	return HIF_KEY_TYPE_AES_GROUP;
+}
+
+static uint8_t fill_sms4_pair(struct hif_wapi_pairwise_key *msg,
+			      struct ieee80211_key_conf *key, u8 *peer_addr)
+{
+	uint8_t *keybuf = key->key;
+
+	WARN_ON(key->keylen != sizeof(msg->wapi_key_data)
+			       + sizeof(msg->mic_key_data));
+	ether_addr_copy(msg->peer_address, peer_addr);
+	memcpy(msg->wapi_key_data, keybuf, sizeof(msg->wapi_key_data));
+	keybuf += sizeof(msg->wapi_key_data);
+	memcpy(msg->mic_key_data, keybuf, sizeof(msg->mic_key_data));
+	msg->key_id = key->keyidx;
+	return HIF_KEY_TYPE_WAPI_PAIRWISE;
+}
+
+static uint8_t fill_sms4_group(struct hif_wapi_group_key *msg,
+			       struct ieee80211_key_conf *key)
+{
+	uint8_t *keybuf = key->key;
+
+	WARN_ON(key->keylen != sizeof(msg->wapi_key_data)
+			       + sizeof(msg->mic_key_data));
+	memcpy(msg->wapi_key_data, keybuf, sizeof(msg->wapi_key_data));
+	keybuf += sizeof(msg->wapi_key_data);
+	memcpy(msg->mic_key_data, keybuf, sizeof(msg->mic_key_data));
+	msg->key_id = key->keyidx;
+	return HIF_KEY_TYPE_WAPI_GROUP;
+}
+
+static uint8_t fill_aes_cmac_group(struct hif_igtk_group_key *msg,
+				   struct ieee80211_key_conf *key,
+				   struct ieee80211_key_seq *seq)
+{
+	WARN_ON(key->keylen != sizeof(msg->igtk_key_data));
+	memcpy(msg->igtk_key_data, key->key, key->keylen);
+	memcpy(msg->ipn, seq->aes_cmac.pn, sizeof(seq->aes_cmac.pn));
+	memreverse(msg->ipn, sizeof(seq->aes_cmac.pn));
+	msg->key_id = key->keyidx;
+	return HIF_KEY_TYPE_IGTK_GROUP;
+}
+
+static int wfx_add_key(struct wfx_vif *wvif, struct ieee80211_sta *sta,
+		       struct ieee80211_key_conf *key)
+{
+	int ret;
+	struct hif_req_add_key *k;
+	struct ieee80211_key_seq seq;
+	struct wfx_dev *wdev = wvif->wdev;
+	int idx = wfx_alloc_key(wvif->wdev);
+	bool pairwise = key->flags & IEEE80211_KEY_FLAG_PAIRWISE;
+
+	WARN_ON(key->flags & IEEE80211_KEY_FLAG_PAIRWISE && !sta);
+	ieee80211_get_key_rx_seq(key, 0, &seq);
+	if (idx < 0)
+		return -EINVAL;
+	k = &wdev->keys[idx];
+	k->int_id = wvif->id;
+	if (key->cipher == WLAN_CIPHER_SUITE_WEP40 || key->cipher ==  WLAN_CIPHER_SUITE_WEP104) {
+		if (pairwise)
+			k->type = fill_wep_pair(&k->key.wep_pairwise_key, key, sta->addr);
+		else
+			k->type = fill_wep_group(&k->key.wep_group_key, key);
+	} else if (key->cipher == WLAN_CIPHER_SUITE_TKIP) {
+		if (pairwise)
+			k->type = fill_tkip_pair(&k->key.tkip_pairwise_key, key, sta->addr);
+		else
+			k->type = fill_tkip_group(&k->key.tkip_group_key, key, &seq, wvif->vif->type);
+	} else if (key->cipher == WLAN_CIPHER_SUITE_CCMP) {
+		if (pairwise)
+			k->type = fill_ccmp_pair(&k->key.aes_pairwise_key, key, sta->addr);
+		else
+			k->type = fill_ccmp_group(&k->key.aes_group_key, key, &seq);
+	} else if (key->cipher ==  WLAN_CIPHER_SUITE_SMS4) {
+		if (pairwise)
+			k->type = fill_sms4_pair(&k->key.wapi_pairwise_key, key, sta->addr);
+		else
+			k->type = fill_sms4_group(&k->key.wapi_group_key, key);
+	} else if (key->cipher ==  WLAN_CIPHER_SUITE_AES_CMAC) {
+		k->type = fill_aes_cmac_group(&k->key.igtk_group_key, key, &seq);
+	} else {
+		dev_warn(wdev->dev, "unsupported key type %d\n", key->cipher);
+		wfx_free_key(wdev, idx);
+		return -EOPNOTSUPP;
+	}
+	ret = hif_add_key(wdev, k);
+	if (ret) {
+		wfx_free_key(wdev, idx);
+		return -EOPNOTSUPP;
+	}
+	key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE |
+		      IEEE80211_KEY_FLAG_RESERVE_TAILROOM;
+	key->hw_key_idx = idx;
+	return 0;
+}
+
+static int wfx_remove_key(struct wfx_vif *wvif, struct ieee80211_key_conf *key)
+{
+	WARN(key->hw_key_idx >= MAX_KEY_ENTRIES, "corrupted hw_key_idx");
+	wfx_free_key(wvif->wdev, key->hw_key_idx);
+	return hif_remove_key(wvif->wdev, key->hw_key_idx);
+}
+
+int wfx_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+		struct ieee80211_vif *vif, struct ieee80211_sta *sta,
+		struct ieee80211_key_conf *key)
+{
+	int ret = -EOPNOTSUPP;
+	struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+
+	mutex_lock(&wvif->wdev->conf_mutex);
+	if (cmd == SET_KEY)
+		ret = wfx_add_key(wvif, sta, key);
+	if (cmd == DISABLE_KEY)
+		ret = wfx_remove_key(wvif, key);
+	mutex_unlock(&wvif->wdev->conf_mutex);
+	return ret;
+}
+
+int wfx_upload_keys(struct wfx_vif *wvif)
+{
+	int i;
+	struct hif_req_add_key *key;
+	struct wfx_dev *wdev = wvif->wdev;
+
+	for (i = 0; i < ARRAY_SIZE(wdev->keys); i++) {
+		if (wdev->key_map & BIT(i)) {
+			key = &wdev->keys[i];
+			if (key->int_id == wvif->id)
+				hif_add_key(wdev, key);
+		}
+	}
+	return 0;
+}
+
+void wfx_wep_key_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif = container_of(work, struct wfx_vif, wep_key_work);
+
+	wfx_tx_flush(wvif->wdev);
+	hif_wep_default_key_id(wvif, wvif->wep_default_key_id);
+	wfx_pending_requeue(wvif->wdev, wvif->wep_pending_skb);
+	wvif->wep_pending_skb = NULL;
+	wfx_tx_unlock(wvif->wdev);
+}
diff --git a/drivers/staging/wfx/key.h b/drivers/staging/wfx/key.h
new file mode 100644
index 000000000000..9436ccdf4d3b
--- /dev/null
+++ b/drivers/staging/wfx/key.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Implementation of mac80211 API.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#ifndef WFX_KEY_H
+#define WFX_KEY_H
+
+#include <net/mac80211.h>
+
+struct wfx_dev;
+struct wfx_vif;
+
+int wfx_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+		struct ieee80211_vif *vif, struct ieee80211_sta *sta,
+		struct ieee80211_key_conf *key);
+int wfx_upload_keys(struct wfx_vif *wvif);
+void wfx_wep_key_work(struct work_struct *work);
+
+#endif /* WFX_STA_H */
diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c
index 06220bac5b75..e7bba24aae0b 100644
--- a/drivers/staging/wfx/main.c
+++ b/drivers/staging/wfx/main.c
@@ -27,6 +27,7 @@
 #include "bus.h"
 #include "bh.h"
 #include "sta.h"
+#include "key.h"
 #include "debug.h"
 #include "data_tx.h"
 #include "secure_link.h"
@@ -56,6 +57,7 @@ static const struct ieee80211_ops wfx_ops = {
 	.remove_interface	= wfx_remove_interface,
 	.tx			= wfx_tx,
 	.hw_scan		= wfx_hw_scan,
+	.set_key		= wfx_set_key,
 };
 
 bool wfx_api_older_than(struct wfx_dev *wdev, int major, int minor)
diff --git a/drivers/staging/wfx/sta.c b/drivers/staging/wfx/sta.c
index 5a8140100e97..2e709b8a3bf4 100644
--- a/drivers/staging/wfx/sta.c
+++ b/drivers/staging/wfx/sta.c
@@ -9,6 +9,7 @@
 
 #include "sta.h"
 #include "wfx.h"
+#include "key.h"
 #include "scan.h"
 #include "hif_tx_mib.h"
 
@@ -162,6 +163,9 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 	INIT_WORK(&wvif->mcast_stop_work, wfx_mcast_stop_work);
 	timer_setup(&wvif->mcast_timeout, wfx_mcast_timeout, 0);
 
+	wvif->wep_default_key_id = -1;
+	INIT_WORK(&wvif->wep_key_work, wfx_wep_key_work);
+
 	sema_init(&wvif->scan.lock, 1);
 	INIT_WORK(&wvif->scan.work, wfx_scan_work);
 	INIT_DELAYED_WORK(&wvif->scan.timeout, wfx_scan_timeout);
diff --git a/drivers/staging/wfx/wfx.h b/drivers/staging/wfx/wfx.h
index 50c0d9c0e528..a86ddfaed825 100644
--- a/drivers/staging/wfx/wfx.h
+++ b/drivers/staging/wfx/wfx.h
@@ -48,6 +48,9 @@ struct wfx_dev {
 	int			tx_burst_idx;
 	atomic_t		tx_lock;
 
+	u32			key_map;
+	struct hif_req_add_key	keys[MAX_KEY_ENTRIES];
+
 	struct hif_rx_stats	rx_stats;
 	struct mutex		rx_stats_lock;
 
@@ -73,6 +76,9 @@ struct wfx_vif {
 	struct work_struct	mcast_start_work;
 	struct work_struct	mcast_stop_work;
 
+	s8			wep_default_key_id;
+	struct sk_buff		*wep_pending_skb;
+	struct work_struct	wep_key_work;
 
 	struct tx_policy_cache	tx_policy_cache;
 	struct work_struct	tx_policy_upload_work;
@@ -120,6 +126,19 @@ static inline struct wfx_vif *wvif_iterate(struct wfx_dev *wdev, struct wfx_vif
 	return NULL;
 }
 
+static inline void memreverse(uint8_t *src, uint8_t length)
+{
+	uint8_t *lo = src;
+	uint8_t *hi = src + length - 1;
+	uint8_t swap;
+
+	while (lo < hi) {
+		swap = *lo;
+		*lo++ = *hi;
+		*hi-- = swap;
+	}
+}
+
 static inline int memzcmp(void *src, unsigned int size)
 {
 	uint8_t *buf = src;
-- 
2.20.1

^ permalink raw reply related

* [PATCH v2 20/20] staging: wfx: implement the rest of mac80211 API
From: Jerome Pouiller @ 2019-09-19 13:52 UTC (permalink / raw)
  To: devel@driverdev.osuosl.org, linux-wireless@vger.kernel.org
  Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	Greg Kroah-Hartman, Kalle Valo, David S . Miller, David Le Goff,
	Jerome Pouiller
In-Reply-To: <20190919135220.30663-1-Jerome.Pouiller@silabs.com>

From: Jérôme Pouiller <jerome.pouiller@silabs.com>

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
 drivers/staging/wfx/data_rx.c |   26 +
 drivers/staging/wfx/data_tx.c |   16 +
 drivers/staging/wfx/debug.c   |    2 +
 drivers/staging/wfx/hif_rx.c  |   53 ++
 drivers/staging/wfx/hif_tx.c  |    1 +
 drivers/staging/wfx/main.c    |  133 +++
 drivers/staging/wfx/queue.c   |   80 ++
 drivers/staging/wfx/scan.c    |   40 +
 drivers/staging/wfx/sta.c     | 1443 ++++++++++++++++++++++++++++++++-
 drivers/staging/wfx/sta.h     |   65 ++
 drivers/staging/wfx/wfx.h     |   51 ++
 11 files changed, 1907 insertions(+), 3 deletions(-)

diff --git a/drivers/staging/wfx/data_rx.c b/drivers/staging/wfx/data_rx.c
index 3b3117b2edac..3a79089c8501 100644
--- a/drivers/staging/wfx/data_rx.c
+++ b/drivers/staging/wfx/data_rx.c
@@ -21,6 +21,8 @@ static int wfx_handle_pspoll(struct wfx_vif *wvif, struct sk_buff *skb)
 	u32 pspoll_mask = 0;
 	int i;
 
+	if (wvif->state != WFX_STATE_AP)
+		return 1;
 	if (!ether_addr_equal(wvif->vif->addr, pspoll->bssid))
 		return 1;
 
@@ -162,6 +164,30 @@ void wfx_rx_cb(struct wfx_vif *wvif, struct hif_ind_rx *arg, struct sk_buff *skb
 	    && arg->rx_flags.match_uc_addr
 	    && mgmt->u.action.category == WLAN_CATEGORY_BACK)
 		goto drop;
+	if (ieee80211_is_beacon(frame->frame_control)
+	    && !arg->status && wvif->vif
+	    && ether_addr_equal(ieee80211_get_SA(frame), wvif->vif->bss_conf.bssid)) {
+		const u8 *tim_ie;
+		u8 *ies = mgmt->u.beacon.variable;
+		size_t ies_len = skb->len - (ies - skb->data);
+
+		tim_ie = cfg80211_find_ie(WLAN_EID_TIM, ies, ies_len);
+		if (tim_ie) {
+			struct ieee80211_tim_ie *tim = (struct ieee80211_tim_ie *) &tim_ie[2];
+
+			if (wvif->dtim_period != tim->dtim_period) {
+				wvif->dtim_period = tim->dtim_period;
+				schedule_work(&wvif->set_beacon_wakeup_period_work);
+			}
+		}
+
+		/* Disable beacon filter once we're associated... */
+		if (wvif->disable_beacon_filter &&
+		    (wvif->vif->bss_conf.assoc || wvif->vif->bss_conf.ibss_joined)) {
+			wvif->disable_beacon_filter = false;
+			schedule_work(&wvif->update_filtering_work);
+		}
+	}
 
 	if (early_data) {
 		spin_lock_bh(&wvif->ps_state_lock);
diff --git a/drivers/staging/wfx/data_tx.c b/drivers/staging/wfx/data_tx.c
index 217d3c270706..7f2799fbdafe 100644
--- a/drivers/staging/wfx/data_tx.c
+++ b/drivers/staging/wfx/data_tx.c
@@ -10,6 +10,7 @@
 #include "data_tx.h"
 #include "wfx.h"
 #include "bh.h"
+#include "sta.h"
 #include "queue.h"
 #include "debug.h"
 #include "traces.h"
@@ -359,6 +360,9 @@ void wfx_link_id_gc_work(struct work_struct *work)
 	u32 mask;
 	int i;
 
+	if (wvif->state != WFX_STATE_AP)
+		return;
+
 	wfx_tx_lock_flush(wvif->wdev);
 	spin_lock_bh(&wvif->ps_state_lock);
 	for (i = 0; i < WFX_MAX_STA_IN_AP_MODE; ++i) {
@@ -729,14 +733,26 @@ void wfx_tx_confirm_cb(struct wfx_vif *wvif, struct hif_cnf_tx *arg)
 	memset(tx_info->pad, 0, sizeof(tx_info->pad));
 
 	if (!arg->status) {
+		if (wvif->bss_loss_state && arg->packet_id == wvif->bss_loss_confirm_id)
+			wfx_cqm_bssloss_sm(wvif, 0, 1, 0);
 		tx_info->status.tx_time = arg->media_delay - arg->tx_queue_delay;
 		if (tx_info->flags & IEEE80211_TX_CTL_NO_ACK)
 			tx_info->flags |= IEEE80211_TX_STAT_NOACK_TRANSMITTED;
 		else
 			tx_info->flags |= IEEE80211_TX_STAT_ACK;
 	} else if (arg->status == HIF_REQUEUE) {
+		/* "REQUEUE" means "implicit suspend" */
+		struct hif_ind_suspend_resume_tx suspend = {
+			.suspend_resume_flags.resume = 0,
+			.suspend_resume_flags.bc_mc_only = 1,
+		};
+
 		WARN(!arg->tx_result_flags.requeue, "incoherent status and result_flags");
+		wfx_suspend_resume(wvif, &suspend);
 		tx_info->flags |= IEEE80211_TX_STAT_TX_FILTERED;
+	} else {
+		if (wvif->bss_loss_state && arg->packet_id == wvif->bss_loss_confirm_id)
+			wfx_cqm_bssloss_sm(wvif, 0, 0, 1);
 	}
 	wfx_pending_remove(wvif->wdev, skb);
 }
diff --git a/drivers/staging/wfx/debug.c b/drivers/staging/wfx/debug.c
index 1e23bb5bde3e..3261b267c385 100644
--- a/drivers/staging/wfx/debug.c
+++ b/drivers/staging/wfx/debug.c
@@ -11,7 +11,9 @@
 
 #include "debug.h"
 #include "wfx.h"
+#include "sta.h"
 #include "main.h"
+#include "hif_tx.h"
 #include "hif_tx_mib.h"
 
 #define CREATE_TRACE_POINTS
diff --git a/drivers/staging/wfx/hif_rx.c b/drivers/staging/wfx/hif_rx.c
index d386fab0a90f..52db02d3aa41 100644
--- a/drivers/staging/wfx/hif_rx.c
+++ b/drivers/staging/wfx/hif_rx.c
@@ -12,6 +12,8 @@
 #include "hif_rx.h"
 #include "wfx.h"
 #include "scan.h"
+#include "bh.h"
+#include "sta.h"
 #include "data_rx.h"
 #include "secure_link.h"
 #include "hif_api_cmd.h"
@@ -144,6 +146,43 @@ static int hif_receive_indication(struct wfx_dev *wdev, struct hif_msg *hif, voi
 	return 0;
 }
 
+static int hif_event_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
+{
+	struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
+	struct hif_ind_event *body = buf;
+	struct wfx_hif_event *event;
+	int first;
+
+	WARN_ON(!wvif);
+	if (!wvif)
+		return 0;
+
+	event = kzalloc(sizeof(*event), GFP_KERNEL);
+	if (!event)
+		return -ENOMEM;
+
+	memcpy(&event->evt, body, sizeof(struct hif_ind_event));
+	spin_lock(&wvif->event_queue_lock);
+	first = list_empty(&wvif->event_queue);
+	list_add_tail(&event->link, &wvif->event_queue);
+	spin_unlock(&wvif->event_queue_lock);
+
+	if (first)
+		schedule_work(&wvif->event_handler_work);
+
+	return 0;
+}
+
+static int hif_pm_mode_complete_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
+{
+	struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
+
+	WARN_ON(!wvif);
+	complete(&wvif->set_pm_mode_complete);
+
+	return 0;
+}
+
 static int hif_scan_complete_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
 {
 	struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
@@ -165,6 +204,17 @@ static int hif_join_complete_indication(struct wfx_dev *wdev, struct hif_msg *hi
 	return 0;
 }
 
+static int hif_suspend_resume_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
+{
+	struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
+	struct hif_ind_suspend_resume_tx *body = buf;
+
+	WARN_ON(!wvif);
+	wfx_suspend_resume(wvif, body);
+
+	return 0;
+}
+
 static int hif_error_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
 {
 	struct hif_ind_error *body = buf;
@@ -242,8 +292,11 @@ static const struct {
 	{ HIF_IND_ID_STARTUP,              hif_startup_indication },
 	{ HIF_IND_ID_WAKEUP,               hif_wakeup_indication },
 	{ HIF_IND_ID_JOIN_COMPLETE,        hif_join_complete_indication },
+	{ HIF_IND_ID_SET_PM_MODE_CMPL,     hif_pm_mode_complete_indication },
 	{ HIF_IND_ID_SCAN_CMPL,            hif_scan_complete_indication },
+	{ HIF_IND_ID_SUSPEND_RESUME_TX,    hif_suspend_resume_indication },
 	{ HIF_IND_ID_SL_EXCHANGE_PUB_KEYS, hif_keys_indication },
+	{ HIF_IND_ID_EVENT,                hif_event_indication },
 	{ HIF_IND_ID_GENERIC,              hif_generic_indication },
 	{ HIF_IND_ID_ERROR,                hif_error_indication },
 	{ HIF_IND_ID_EXCEPTION,            hif_exception_indication },
diff --git a/drivers/staging/wfx/hif_tx.c b/drivers/staging/wfx/hif_tx.c
index 157ab177b73f..2d40225a0fce 100644
--- a/drivers/staging/wfx/hif_tx.c
+++ b/drivers/staging/wfx/hif_tx.c
@@ -14,6 +14,7 @@
 #include "bh.h"
 #include "hwio.h"
 #include "debug.h"
+#include "sta.h"
 
 void wfx_init_hif_cmd(struct wfx_hif_cmd *hif_cmd)
 {
diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c
index e7bba24aae0b..fe9a89703897 100644
--- a/drivers/staging/wfx/main.c
+++ b/drivers/staging/wfx/main.c
@@ -50,14 +50,112 @@ static char *slk_key;
 module_param(slk_key, charp, 0600);
 MODULE_PARM_DESC(slk_key, "secret key for secure link (expect 64 hexdecimal digits).");
 
+#define RATETAB_ENT(_rate, _rateid, _flags) { \
+	.bitrate  = (_rate),   \
+	.hw_value = (_rateid), \
+	.flags    = (_flags),  \
+}
+
+static struct ieee80211_rate wfx_rates[] = {
+	RATETAB_ENT(10,  0,  0),
+	RATETAB_ENT(20,  1,  IEEE80211_RATE_SHORT_PREAMBLE),
+	RATETAB_ENT(55,  2,  IEEE80211_RATE_SHORT_PREAMBLE),
+	RATETAB_ENT(110, 3,  IEEE80211_RATE_SHORT_PREAMBLE),
+	RATETAB_ENT(60,  6,  0),
+	RATETAB_ENT(90,  7,  0),
+	RATETAB_ENT(120, 8,  0),
+	RATETAB_ENT(180, 9,  0),
+	RATETAB_ENT(240, 10, 0),
+	RATETAB_ENT(360, 11, 0),
+	RATETAB_ENT(480, 12, 0),
+	RATETAB_ENT(540, 13, 0),
+};
+
+#define CHAN2G(_channel, _freq, _flags) { \
+	.band = NL80211_BAND_2GHZ, \
+	.center_freq = (_freq),    \
+	.hw_value = (_channel),    \
+	.flags = (_flags),         \
+	.max_antenna_gain = 0,     \
+	.max_power = 30,           \
+}
+
+static struct ieee80211_channel wfx_2ghz_chantable[] = {
+	CHAN2G(1,  2412, 0),
+	CHAN2G(2,  2417, 0),
+	CHAN2G(3,  2422, 0),
+	CHAN2G(4,  2427, 0),
+	CHAN2G(5,  2432, 0),
+	CHAN2G(6,  2437, 0),
+	CHAN2G(7,  2442, 0),
+	CHAN2G(8,  2447, 0),
+	CHAN2G(9,  2452, 0),
+	CHAN2G(10, 2457, 0),
+	CHAN2G(11, 2462, 0),
+	CHAN2G(12, 2467, 0),
+	CHAN2G(13, 2472, 0),
+	CHAN2G(14, 2484, 0),
+};
+
+static const struct ieee80211_supported_band wfx_band_2ghz = {
+	.channels = wfx_2ghz_chantable,
+	.n_channels = ARRAY_SIZE(wfx_2ghz_chantable),
+	.bitrates = wfx_rates,
+	.n_bitrates = ARRAY_SIZE(wfx_rates),
+	.ht_cap = {
+		// Receive caps
+		.cap = IEEE80211_HT_CAP_GRN_FLD | IEEE80211_HT_CAP_SGI_20 |
+		       IEEE80211_HT_CAP_MAX_AMSDU | (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT),
+		.ht_supported = 1,
+		.ampdu_factor = IEEE80211_HT_MAX_AMPDU_16K,
+		.ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE,
+		.mcs = {
+			.rx_mask = { 0xFF }, // MCS0 to MCS7
+			.rx_highest = 65,
+			.tx_params = IEEE80211_HT_MCS_TX_DEFINED,
+		},
+	},
+};
+
+static const struct ieee80211_iface_limit wdev_iface_limits[] = {
+	{ .max = 1, .types = BIT(NL80211_IFTYPE_STATION) },
+	{ .max = 1, .types = BIT(NL80211_IFTYPE_AP) },
+};
+
+static const struct ieee80211_iface_combination wfx_iface_combinations[] = {
+	{
+		.num_different_channels = 2,
+		.max_interfaces = 2,
+		.limits = wdev_iface_limits,
+		.n_limits = ARRAY_SIZE(wdev_iface_limits),
+	}
+};
+
 static const struct ieee80211_ops wfx_ops = {
 	.start			= wfx_start,
 	.stop			= wfx_stop,
 	.add_interface		= wfx_add_interface,
 	.remove_interface	= wfx_remove_interface,
+	.config			= wfx_config,
 	.tx			= wfx_tx,
+	.conf_tx		= wfx_conf_tx,
 	.hw_scan		= wfx_hw_scan,
+	.sta_add		= wfx_sta_add,
+	.sta_remove		= wfx_sta_remove,
+	.sta_notify		= wfx_sta_notify,
+	.set_tim		= wfx_set_tim,
 	.set_key		= wfx_set_key,
+	.set_rts_threshold	= wfx_set_rts_threshold,
+	.bss_info_changed	= wfx_bss_info_changed,
+	.prepare_multicast	= wfx_prepare_multicast,
+	.configure_filter	= wfx_configure_filter,
+	.ampdu_action		= wfx_ampdu_action,
+	.flush			= wfx_flush,
+	.add_chanctx		= wfx_add_chanctx,
+	.remove_chanctx		= wfx_remove_chanctx,
+	.change_chanctx		= wfx_change_chanctx,
+	.assign_vif_chanctx	= wfx_assign_vif_chanctx,
+	.unassign_vif_chanctx	= wfx_unassign_vif_chanctx,
 };
 
 bool wfx_api_older_than(struct wfx_dev *wdev, int major, int minor)
@@ -198,6 +296,16 @@ struct wfx_dev *wfx_init_common(struct device *dev,
 
 	SET_IEEE80211_DEV(hw, dev);
 
+	ieee80211_hw_set(hw, NEED_DTIM_BEFORE_ASSOC);
+	ieee80211_hw_set(hw, TX_AMPDU_SETUP_IN_HW);
+	ieee80211_hw_set(hw, AMPDU_AGGREGATION);
+	ieee80211_hw_set(hw, CONNECTION_MONITOR);
+	ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS);
+	ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS);
+	ieee80211_hw_set(hw, SIGNAL_DBM);
+	ieee80211_hw_set(hw, SUPPORTS_PS);
+	ieee80211_hw_set(hw, MFP_CAPABLE);
+
 	hw->vif_data_size = sizeof(struct wfx_vif);
 	hw->sta_data_size = sizeof(struct wfx_sta_priv);
 	hw->queues = 4;
@@ -206,8 +314,19 @@ struct wfx_dev *wfx_init_common(struct device *dev,
 	hw->extra_tx_headroom = sizeof(struct hif_sl_msg_hdr) + sizeof(struct hif_msg)
 				+ sizeof(struct hif_req_tx)
 				+ 4 /* alignment */ + 8 /* TKIP IV */;
+	hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
+				     BIT(NL80211_IFTYPE_ADHOC) |
+				     BIT(NL80211_IFTYPE_AP);
+	hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD;
+	hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT;
+	hw->wiphy->max_ap_assoc_sta = WFX_MAX_STA_IN_AP_MODE;
 	hw->wiphy->max_scan_ssids = 2;
 	hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN;
+	hw->wiphy->n_iface_combinations = ARRAY_SIZE(wfx_iface_combinations);
+	hw->wiphy->iface_combinations = wfx_iface_combinations;
+	hw->wiphy->bands[NL80211_BAND_2GHZ] = devm_kmalloc(dev, sizeof(wfx_band_2ghz), GFP_KERNEL);
+	// FIXME: also copy wfx_rates and wfx_2ghz_chantable
+	memcpy(hw->wiphy->bands[NL80211_BAND_2GHZ], &wfx_band_2ghz, sizeof(wfx_band_2ghz));
 
 	wdev = hw->priv;
 	wdev->hw = hw;
@@ -290,6 +409,12 @@ int wfx_probe(struct wfx_dev *wdev)
 		goto err1;
 	}
 
+	if (wdev->hw_caps.regul_sel_mode_info.region_sel_mode) {
+		wdev->hw->wiphy->bands[NL80211_BAND_2GHZ]->channels[11].flags |= IEEE80211_CHAN_NO_IR;
+		wdev->hw->wiphy->bands[NL80211_BAND_2GHZ]->channels[12].flags |= IEEE80211_CHAN_NO_IR;
+		wdev->hw->wiphy->bands[NL80211_BAND_2GHZ]->channels[13].flags |= IEEE80211_CHAN_DISABLED;
+	}
+
 	dev_dbg(wdev->dev, "sending configuration file %s\n", wdev->pdata.file_pds);
 	err = wfx_send_pdata_pds(wdev);
 	if (err < 0)
@@ -322,6 +447,12 @@ int wfx_probe(struct wfx_dev *wdev)
 		}
 		dev_info(wdev->dev, "MAC address %d: %pM\n", i, wdev->addresses[i].addr);
 	}
+	wdev->hw->wiphy->n_addresses = ARRAY_SIZE(wdev->addresses);
+	wdev->hw->wiphy->addresses = wdev->addresses;
+
+	err = ieee80211_register_hw(wdev->hw);
+	if (err)
+		goto err1;
 
 	err = wfx_debug_init(wdev);
 	if (err)
@@ -330,6 +461,7 @@ int wfx_probe(struct wfx_dev *wdev)
 	return 0;
 
 err2:
+	ieee80211_unregister_hw(wdev->hw);
 	ieee80211_free_hw(wdev->hw);
 err1:
 	wfx_bh_unregister(wdev);
@@ -338,6 +470,7 @@ int wfx_probe(struct wfx_dev *wdev)
 
 void wfx_release(struct wfx_dev *wdev)
 {
+	ieee80211_unregister_hw(wdev->hw);
 	hif_shutdown(wdev);
 	wfx_bh_unregister(wdev);
 	wfx_sl_deinit(wdev);
diff --git a/drivers/staging/wfx/queue.c b/drivers/staging/wfx/queue.c
index aa438be21d37..6f1be4f6f463 100644
--- a/drivers/staging/wfx/queue.c
+++ b/drivers/staging/wfx/queue.c
@@ -351,6 +351,83 @@ bool wfx_tx_queues_is_empty(struct wfx_dev *wdev)
 	return ret;
 }
 
+static bool hif_handle_tx_data(struct wfx_vif *wvif, struct sk_buff *skb,
+			       struct wfx_queue *queue)
+{
+	bool handled = false;
+	struct wfx_tx_priv *tx_priv = wfx_skb_tx_priv(skb);
+	struct hif_req_tx *req = wfx_skb_txreq(skb);
+	struct ieee80211_hdr *frame = (struct ieee80211_hdr *) (req->frame + req->data_flags.fc_offset);
+
+	enum {
+		do_probe,
+		do_drop,
+		do_wep,
+		do_tx,
+	} action = do_tx;
+
+	switch (wvif->vif->type) {
+	case NL80211_IFTYPE_STATION:
+		if (wvif->state < WFX_STATE_PRE_STA)
+			action = do_drop;
+		break;
+	case NL80211_IFTYPE_AP:
+		if (!wvif->state) {
+			action = do_drop;
+		} else if (!(BIT(tx_priv->raw_link_id) & (BIT(0) | wvif->link_id_map))) {
+			dev_warn(wvif->wdev->dev, "a frame with expired link-id is dropped\n");
+			action = do_drop;
+		}
+		break;
+	case NL80211_IFTYPE_ADHOC:
+		if (wvif->state != WFX_STATE_IBSS)
+			action = do_drop;
+		break;
+	case NL80211_IFTYPE_MONITOR:
+	default:
+		action = do_drop;
+		break;
+	}
+
+	if (action == do_tx) {
+		if (ieee80211_is_nullfunc(frame->frame_control)) {
+			mutex_lock(&wvif->bss_loss_lock);
+			if (wvif->bss_loss_state) {
+				wvif->bss_loss_confirm_id = req->packet_id;
+				req->queue_id.queue_id = HIF_QUEUE_ID_VOICE;
+			}
+			mutex_unlock(&wvif->bss_loss_lock);
+		} else if (ieee80211_has_protected(frame->frame_control) &&
+			   tx_priv->hw_key &&
+			   tx_priv->hw_key->keyidx != wvif->wep_default_key_id &&
+			   (tx_priv->hw_key->cipher == WLAN_CIPHER_SUITE_WEP40 ||
+			    tx_priv->hw_key->cipher == WLAN_CIPHER_SUITE_WEP104)) {
+			action = do_wep;
+		}
+	}
+
+	switch (action) {
+	case do_drop:
+		BUG_ON(wfx_pending_remove(wvif->wdev, skb));
+		handled = true;
+		break;
+	case do_wep:
+		wfx_tx_lock(wvif->wdev);
+		wvif->wep_default_key_id = tx_priv->hw_key->keyidx;
+		wvif->wep_pending_skb = skb;
+		if (!schedule_work(&wvif->wep_key_work))
+			wfx_tx_unlock(wvif->wdev);
+		handled = true;
+		break;
+	case do_tx:
+		break;
+	default:
+		/* Do nothing */
+		break;
+	}
+	return handled;
+}
+
 static int wfx_get_prio_queue(struct wfx_vif *wvif,
 				 u32 tx_allowed_mask, int *total)
 {
@@ -498,6 +575,9 @@ struct hif_msg *wfx_tx_queues_get(struct wfx_dev *wdev)
 		wvif = wdev_to_wvif(wdev, hif->interface);
 		WARN_ON(!wvif);
 
+		if (hif_handle_tx_data(wvif, skb, queue))
+			continue;  /* Handled by WSM */
+
 		wvif->pspoll_mask &= ~BIT(tx_priv->raw_link_id);
 
 		/* allow bursting if txop is set */
diff --git a/drivers/staging/wfx/scan.c b/drivers/staging/wfx/scan.c
index 207b26ebc9fd..ea5001c915f6 100644
--- a/drivers/staging/wfx/scan.c
+++ b/drivers/staging/wfx/scan.c
@@ -21,11 +21,26 @@ static void __ieee80211_scan_completed_compat(struct ieee80211_hw *hw, bool abor
 	ieee80211_scan_completed(hw, &info);
 }
 
+static void wfx_scan_restart_delayed(struct wfx_vif *wvif)
+{
+	if (wvif->delayed_unjoin) {
+		wvif->delayed_unjoin = false;
+		if (!schedule_work(&wvif->unjoin_work))
+			wfx_tx_unlock(wvif->wdev);
+	} else if (wvif->delayed_link_loss) {
+		wvif->delayed_link_loss = 0;
+		wfx_cqm_bssloss_sm(wvif, 1, 0, 0);
+	}
+}
+
 static int wfx_scan_start(struct wfx_vif *wvif, struct wfx_scan_params *scan)
 {
 	int ret;
 	int tmo = 500;
 
+	if (wvif->state == WFX_STATE_PRE_STA)
+		return -EBUSY;
+
 	tmo += scan->scan_req.num_of_channels *
 	       ((20 * (scan->scan_req.max_channel_time)) + 10);
 	atomic_set(&wvif->scan.in_progress, 1);
@@ -38,6 +53,7 @@ static int wfx_scan_start(struct wfx_vif *wvif, struct wfx_scan_params *scan)
 		atomic_set(&wvif->scan.in_progress, 0);
 		atomic_set(&wvif->wdev->scan_in_progress, 0);
 		cancel_delayed_work_sync(&wvif->scan.timeout);
+		wfx_scan_restart_delayed(wvif);
 	}
 	return ret;
 }
@@ -56,6 +72,9 @@ int wfx_hw_scan(struct ieee80211_hw *hw,
 	if (!wvif)
 		return -EINVAL;
 
+	if (wvif->state == WFX_STATE_AP)
+		return -EOPNOTSUPP;
+
 	if (req->n_ssids == 1 && !req->ssids[0].ssid_len)
 		req->n_ssids = 0;
 
@@ -121,11 +140,23 @@ void wfx_scan_work(struct work_struct *work)
 		.scan_req.scan_type.type = 0,    /* Foreground */
 	};
 	struct ieee80211_channel *first;
+	bool first_run = (wvif->scan.begin == wvif->scan.curr &&
+			  wvif->scan.begin != wvif->scan.end);
 	int i;
 
 	down(&wvif->scan.lock);
 	mutex_lock(&wvif->wdev->conf_mutex);
 
+	if (first_run) {
+		if (wvif->state == WFX_STATE_STA &&
+		    !(wvif->powersave_mode.pm_mode.enter_psm)) {
+			struct hif_req_set_pm_mode pm = wvif->powersave_mode;
+
+			pm.pm_mode.enter_psm = 1;
+			wfx_set_pm(wvif, &pm);
+		}
+	}
+
 	if (!wvif->scan.req || wvif->scan.curr == wvif->scan.end) {
 		if (wvif->scan.output_power != wvif->wdev->output_power)
 			hif_set_output_power(wvif, wvif->wdev->output_power * 10);
@@ -138,10 +169,14 @@ void wfx_scan_work(struct work_struct *work)
 			dev_dbg(wvif->wdev->dev, "scan canceled\n");
 
 		wvif->scan.req = NULL;
+		wfx_scan_restart_delayed(wvif);
 		wfx_tx_unlock(wvif->wdev);
 		mutex_unlock(&wvif->wdev->conf_mutex);
 		__ieee80211_scan_completed_compat(wvif->wdev->hw, wvif->scan.status ? 1 : 0);
 		up(&wvif->scan.lock);
+		if (wvif->state == WFX_STATE_STA &&
+		    !(wvif->powersave_mode.pm_mode.enter_psm))
+			wfx_set_pm(wvif, &wvif->powersave_mode);
 		return;
 	}
 	first = *wvif->scan.curr;
@@ -170,6 +205,11 @@ void wfx_scan_work(struct work_struct *work)
 	scan.ssids = &wvif->scan.ssids[0];
 	scan.scan_req.num_of_channels = it - wvif->scan.curr;
 	scan.scan_req.probe_delay = 100;
+	// FIXME: Check if FW can do active scan while joined.
+	if (wvif->state == WFX_STATE_STA) {
+		scan.scan_req.scan_type.type = 1;
+		scan.scan_req.scan_flags.fbg = 1;
+	}
 
 	scan.ch = kcalloc(scan.scan_req.num_of_channels, sizeof(u8), GFP_KERNEL);
 
diff --git a/drivers/staging/wfx/sta.c b/drivers/staging/wfx/sta.c
index 2e709b8a3bf4..2855d14a709c 100644
--- a/drivers/staging/wfx/sta.c
+++ b/drivers/staging/wfx/sta.c
@@ -9,11 +9,148 @@
 
 #include "sta.h"
 #include "wfx.h"
+#include "fwio.h"
+#include "bh.h"
 #include "key.h"
 #include "scan.h"
+#include "debug.h"
+#include "hif_tx.h"
 #include "hif_tx_mib.h"
 
 #define TXOP_UNIT 32
+#define HIF_MAX_ARP_IP_ADDRTABLE_ENTRIES 2
+
+static u32 wfx_rate_mask_to_hw(struct wfx_dev *wdev, u32 rates)
+{
+	int i;
+	u32 ret = 0;
+	// WFx only support 2GHz
+	struct ieee80211_supported_band *sband = wdev->hw->wiphy->bands[NL80211_BAND_2GHZ];
+
+	for (i = 0; i < sband->n_bitrates; i++) {
+		if (rates & BIT(i)) {
+			if (i >= sband->n_bitrates)
+				dev_warn(wdev->dev, "unsupported basic rate\n");
+			else
+				ret |= BIT(sband->bitrates[i].hw_value);
+		}
+	}
+	return ret;
+}
+
+static void __wfx_free_event_queue(struct list_head *list)
+{
+	struct wfx_hif_event *event, *tmp;
+
+	list_for_each_entry_safe(event, tmp, list, link) {
+		list_del(&event->link);
+		kfree(event);
+	}
+}
+
+static void wfx_free_event_queue(struct wfx_vif *wvif)
+{
+	LIST_HEAD(list);
+
+	spin_lock(&wvif->event_queue_lock);
+	list_splice_init(&wvif->event_queue, &list);
+	spin_unlock(&wvif->event_queue_lock);
+
+	__wfx_free_event_queue(&list);
+}
+
+void wfx_cqm_bssloss_sm(struct wfx_vif *wvif, int init, int good, int bad)
+{
+	int tx = 0;
+
+	mutex_lock(&wvif->bss_loss_lock);
+	wvif->delayed_link_loss = 0;
+	cancel_work_sync(&wvif->bss_params_work);
+
+	/* If we have a pending unjoin */
+	if (wvif->delayed_unjoin)
+		goto end;
+
+	if (init) {
+		schedule_delayed_work(&wvif->bss_loss_work, HZ);
+		wvif->bss_loss_state = 0;
+
+		if (!atomic_read(&wvif->wdev->tx_lock))
+			tx = 1;
+	} else if (good) {
+		cancel_delayed_work_sync(&wvif->bss_loss_work);
+		wvif->bss_loss_state = 0;
+		schedule_work(&wvif->bss_params_work);
+	} else if (bad) {
+		/* FIXME Should we just keep going until we time out? */
+		if (wvif->bss_loss_state < 3)
+			tx = 1;
+	} else {
+		cancel_delayed_work_sync(&wvif->bss_loss_work);
+		wvif->bss_loss_state = 0;
+	}
+
+	/* Spit out a NULL packet to our AP if necessary */
+	// FIXME: call ieee80211_beacon_loss/ieee80211_connection_loss instead
+	if (tx) {
+		struct sk_buff *skb;
+
+		wvif->bss_loss_state++;
+
+		skb = ieee80211_nullfunc_get(wvif->wdev->hw, wvif->vif, false);
+		if (!skb)
+			goto end;
+		memset(IEEE80211_SKB_CB(skb), 0, sizeof(*IEEE80211_SKB_CB(skb)));
+		IEEE80211_SKB_CB(skb)->control.vif = wvif->vif;
+		IEEE80211_SKB_CB(skb)->driver_rates[0].idx = 0;
+		IEEE80211_SKB_CB(skb)->driver_rates[0].count = 1;
+		IEEE80211_SKB_CB(skb)->driver_rates[1].idx = -1;
+		wfx_tx(wvif->wdev->hw, NULL, skb);
+	}
+end:
+	mutex_unlock(&wvif->bss_loss_lock);
+}
+
+static int wfx_set_uapsd_param(struct wfx_vif *wvif,
+			   const struct wfx_edca_params *arg)
+{
+	int ret;
+
+	/* Here's the mapping AC [queue, bit]
+	 *  VO [0,3], VI [1, 2], BE [2, 1], BK [3, 0]
+	 */
+
+	if (arg->uapsd_enable[IEEE80211_AC_VO])
+		wvif->uapsd_info.trig_voice = 1;
+	else
+		wvif->uapsd_info.trig_voice = 0;
+
+	if (arg->uapsd_enable[IEEE80211_AC_VI])
+		wvif->uapsd_info.trig_video = 1;
+	else
+		wvif->uapsd_info.trig_video = 0;
+
+	if (arg->uapsd_enable[IEEE80211_AC_BE])
+		wvif->uapsd_info.trig_be = 1;
+	else
+		wvif->uapsd_info.trig_be = 0;
+
+	if (arg->uapsd_enable[IEEE80211_AC_BK])
+		wvif->uapsd_info.trig_bckgrnd = 1;
+	else
+		wvif->uapsd_info.trig_bckgrnd = 0;
+
+	/* Currently pseudo U-APSD operation is not supported, so setting
+	 * MinAutoTriggerInterval, MaxAutoTriggerInterval and
+	 * AutoTriggerStep to 0
+	 */
+	wvif->uapsd_info.min_auto_trigger_interval = 0;
+	wvif->uapsd_info.max_auto_trigger_interval = 0;
+	wvif->uapsd_info.auto_trigger_step = 0;
+
+	ret = hif_set_uapsd_info(wvif, &wvif->uapsd_info);
+	return ret;
+}
 
 int wfx_fwd_probe_req(struct wfx_vif *wvif, bool enable)
 {
@@ -22,6 +159,1044 @@ int wfx_fwd_probe_req(struct wfx_vif *wvif, bool enable)
 				 wvif->fwd_probe_req);
 }
 
+static int wfx_set_mcast_filter(struct wfx_vif *wvif,
+				    struct wfx_grp_addr_table *fp)
+{
+	int i, ret;
+	struct hif_mib_config_data_filter config = { };
+	struct hif_mib_set_data_filtering filter_data = { };
+	struct hif_mib_mac_addr_data_frame_condition filter_addr_val = { };
+	struct hif_mib_uc_mc_bc_data_frame_condition filter_addr_type = { };
+
+	// Temporary workaround for filters
+	return hif_set_data_filtering(wvif, &filter_data);
+
+	if (!fp->enable) {
+		filter_data.enable = 0;
+		return hif_set_data_filtering(wvif, &filter_data);
+	}
+
+	// A1 Address match on list
+	for (i = 0; i < fp->num_addresses; i++) {
+		filter_addr_val.condition_idx = i;
+		filter_addr_val.address_type = HIF_MAC_ADDR_A1;
+		ether_addr_copy(filter_addr_val.mac_address, fp->address_list[i]);
+		ret = hif_set_mac_addr_condition(wvif, &filter_addr_val);
+		if (ret)
+			return ret;
+		config.mac_cond |= 1 << i;
+	}
+
+	// Accept unicast and broadcast
+	filter_addr_type.condition_idx = 0;
+	filter_addr_type.param.bits.type_unicast = 1;
+	filter_addr_type.param.bits.type_broadcast = 1;
+	ret = hif_set_uc_mc_bc_condition(wvif, &filter_addr_type);
+	if (ret)
+		return ret;
+
+	config.uc_mc_bc_cond = 1;
+	config.filter_idx = 0; // TODO #define MULTICAST_FILTERING 0
+	config.enable = 1;
+	ret = hif_set_config_data_filter(wvif, &config);
+	if (ret)
+		return ret;
+
+	// discard all data frames except match filter
+	filter_data.enable = 1;
+	filter_data.default_filter = 1; // discard all
+	ret = hif_set_data_filtering(wvif, &filter_data);
+
+	return ret;
+}
+
+void wfx_update_filtering(struct wfx_vif *wvif)
+{
+	int ret;
+	bool is_sta = wvif->vif && NL80211_IFTYPE_STATION == wvif->vif->type;
+	bool filter_bssid = wvif->filter_bssid;
+	bool fwd_probe_req = wvif->fwd_probe_req;
+	struct hif_mib_bcn_filter_enable bf_ctrl;
+	struct hif_mib_bcn_filter_table *bf_tbl;
+	struct hif_ie_table_entry ie_tbl[] = {
+		{
+			.ie_id        = WLAN_EID_VENDOR_SPECIFIC,
+			.has_changed  = 1,
+			.no_longer    = 1,
+			.has_appeared = 1,
+			.oui         = { 0x50, 0x6F, 0x9A},
+		}, {
+			.ie_id        = WLAN_EID_HT_OPERATION,
+			.has_changed  = 1,
+			.no_longer    = 1,
+			.has_appeared = 1,
+		}, {
+			.ie_id        = WLAN_EID_ERP_INFO,
+			.has_changed  = 1,
+			.no_longer    = 1,
+			.has_appeared = 1,
+		}
+	};
+
+	if (wvif->state == WFX_STATE_PASSIVE)
+		return;
+
+	bf_tbl = kmalloc(sizeof(struct hif_mib_bcn_filter_table) + sizeof(ie_tbl), GFP_KERNEL);
+	memcpy(bf_tbl->ie_table, ie_tbl, sizeof(ie_tbl));
+	if (wvif->disable_beacon_filter) {
+		bf_ctrl.enable = 0;
+		bf_ctrl.bcn_count = 1;
+		bf_tbl->num_of_info_elmts = 0;
+	} else if (!is_sta) {
+		bf_ctrl.enable = HIF_BEACON_FILTER_ENABLE | HIF_BEACON_FILTER_AUTO_ERP;
+		bf_ctrl.bcn_count = 0;
+		bf_tbl->num_of_info_elmts = 2;
+	} else {
+		bf_ctrl.enable = HIF_BEACON_FILTER_ENABLE;
+		bf_ctrl.bcn_count = 0;
+		bf_tbl->num_of_info_elmts = 3;
+	}
+
+	ret = hif_set_rx_filter(wvif, filter_bssid, fwd_probe_req);
+	if (!ret)
+		ret = hif_set_beacon_filter_table(wvif, bf_tbl);
+	if (!ret)
+		ret = hif_beacon_filter_control(wvif, bf_ctrl.enable, bf_ctrl.bcn_count);
+	if (!ret)
+		ret = wfx_set_mcast_filter(wvif, &wvif->mcast_filter);
+	if (ret)
+		dev_err(wvif->wdev->dev, "update filtering failed: %d\n", ret);
+	kfree(bf_tbl);
+}
+
+void wfx_update_filtering_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif = container_of(work, struct wfx_vif, update_filtering_work);
+
+	wfx_update_filtering(wvif);
+}
+
+u64 wfx_prepare_multicast(struct ieee80211_hw *hw, struct netdev_hw_addr_list *mc_list)
+{
+	int i;
+	struct netdev_hw_addr *ha;
+	struct wfx_vif *wvif = NULL;
+	struct wfx_dev *wdev = hw->priv;
+	int count = netdev_hw_addr_list_count(mc_list);
+
+	while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
+		memset(&wvif->mcast_filter, 0x00, sizeof(wvif->mcast_filter));
+		if (!count || count > ARRAY_SIZE(wvif->mcast_filter.address_list))
+			continue;
+
+		i = 0;
+		netdev_hw_addr_list_for_each(ha, mc_list) {
+			ether_addr_copy(wvif->mcast_filter.address_list[i], ha->addr);
+			i++;
+		}
+		wvif->mcast_filter.enable = 1;
+		wvif->mcast_filter.num_addresses = count;
+	}
+
+	return 0;
+}
+
+void wfx_configure_filter(struct ieee80211_hw *hw,
+			     unsigned int changed_flags,
+			     unsigned int *total_flags,
+			     u64 unused)
+{
+	struct wfx_vif *wvif = NULL;
+	struct wfx_dev *wdev = hw->priv;
+
+	*total_flags &= FIF_OTHER_BSS | FIF_FCSFAIL | FIF_PROBE_REQ;
+
+	while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
+		down(&wvif->scan.lock);
+		wvif->filter_bssid = (*total_flags & (FIF_OTHER_BSS | FIF_PROBE_REQ)) ? 0 : 1;
+		wvif->disable_beacon_filter = !(*total_flags & FIF_PROBE_REQ);
+		wfx_fwd_probe_req(wvif, true);
+		wfx_update_filtering(wvif);
+		up(&wvif->scan.lock);
+	}
+}
+
+int wfx_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		   u16 queue, const struct ieee80211_tx_queue_params *params)
+{
+	struct wfx_dev *wdev = hw->priv;
+	struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+	int ret = 0;
+	/* To prevent re-applying PM request OID again and again*/
+	u16 old_uapsd_flags, new_uapsd_flags;
+	struct hif_req_edca_queue_params *edca;
+
+	mutex_lock(&wdev->conf_mutex);
+
+	if (queue < hw->queues) {
+		old_uapsd_flags = *((u16 *) &wvif->uapsd_info);
+		edca = &wvif->edca.params[queue];
+
+		wvif->edca.uapsd_enable[queue] = params->uapsd;
+		edca->aifsn = params->aifs;
+		edca->cw_min = params->cw_min;
+		edca->cw_max = params->cw_max;
+		edca->tx_op_limit = params->txop * TXOP_UNIT;
+		edca->allowed_medium_time = 0;
+		ret = hif_set_edca_queue_params(wvif, edca);
+		if (ret) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (wvif->vif->type == NL80211_IFTYPE_STATION) {
+			ret = wfx_set_uapsd_param(wvif, &wvif->edca);
+			new_uapsd_flags = *((u16 *) &wvif->uapsd_info);
+			if (!ret && wvif->setbssparams_done &&
+			    wvif->state == WFX_STATE_STA &&
+			    old_uapsd_flags != new_uapsd_flags)
+				ret = wfx_set_pm(wvif, &wvif->powersave_mode);
+		}
+	} else {
+		ret = -EINVAL;
+	}
+
+out:
+	mutex_unlock(&wdev->conf_mutex);
+	return ret;
+}
+
+int wfx_set_pm(struct wfx_vif *wvif, const struct hif_req_set_pm_mode *arg)
+{
+	struct hif_req_set_pm_mode pm = *arg;
+	u16 uapsd_flags;
+	int ret;
+
+	if (wvif->state != WFX_STATE_STA || !wvif->bss_params.aid)
+		return 0;
+
+	memcpy(&uapsd_flags, &wvif->uapsd_info, sizeof(uapsd_flags));
+
+	if (uapsd_flags != 0)
+		pm.pm_mode.fast_psm = 0;
+
+	// Kernel disable PowerSave when multiple vifs are in use. In contrary,
+	// it is absolutly necessary to enable PowerSave for WF200
+	if (wvif_count(wvif->wdev) > 1) {
+		pm.pm_mode.enter_psm = 1;
+		pm.pm_mode.fast_psm = 0;
+	}
+
+	if (!wait_for_completion_timeout(&wvif->set_pm_mode_complete, msecs_to_jiffies(300)))
+		dev_warn(wvif->wdev->dev, "timeout while waiting of set_pm_mode_complete\n");
+	ret = hif_set_pm(wvif, &pm);
+	// FIXME: why ?
+	if (-ETIMEDOUT == wvif->scan.status)
+		wvif->scan.status = 1;
+	return ret;
+}
+
+int wfx_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
+{
+	struct wfx_dev *wdev = hw->priv;
+	struct wfx_vif *wvif = NULL;
+
+	while ((wvif = wvif_iterate(wdev, wvif)) != NULL)
+		hif_rts_threshold(wvif, value);
+	return 0;
+}
+
+/* If successful, LOCKS the TX queue! */
+static int __wfx_flush(struct wfx_dev *wdev, bool drop)
+{
+	int ret;
+
+	for (;;) {
+		if (drop) {
+			wfx_tx_queues_clear(wdev);
+		} else {
+			ret = wait_event_timeout(
+				wdev->tx_queue_stats.wait_link_id_empty,
+				wfx_tx_queues_is_empty(wdev),
+				2 * HZ);
+		}
+
+		if (!drop && ret <= 0) {
+			ret = -ETIMEDOUT;
+			break;
+		}
+		ret = 0;
+
+		wfx_tx_lock_flush(wdev);
+		if (!wfx_tx_queues_is_empty(wdev)) {
+			/* Highly unlikely: WSM requeued frames. */
+			wfx_tx_unlock(wdev);
+			continue;
+		}
+		break;
+	}
+	return ret;
+}
+
+void wfx_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		  u32 queues, bool drop)
+{
+	struct wfx_dev *wdev = hw->priv;
+	struct wfx_vif *wvif;
+
+	if (vif) {
+		wvif = (struct wfx_vif *) vif->drv_priv;
+		if (wvif->vif->type == NL80211_IFTYPE_MONITOR)
+			drop = true;
+		if (wvif->vif->type == NL80211_IFTYPE_AP && !wvif->enable_beacon)
+			drop = true;
+	}
+
+	// FIXME: only flush requested vif
+	if (!__wfx_flush(wdev, drop))
+		wfx_tx_unlock(wdev);
+}
+
+/* WSM callbacks */
+
+static void wfx_event_report_rssi(struct wfx_vif *wvif, uint8_t raw_rcpi_rssi)
+{
+	/* RSSI: signed Q8.0, RCPI: unsigned Q7.1
+	 * RSSI = RCPI / 2 - 110
+	 */
+	int rcpi_rssi;
+	int cqm_evt;
+
+	rcpi_rssi = raw_rcpi_rssi / 2 - 110;
+	if (rcpi_rssi <= wvif->cqm_rssi_thold)
+		cqm_evt = NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW;
+	else
+		cqm_evt = NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH;
+	ieee80211_cqm_rssi_notify(wvif->vif, cqm_evt, rcpi_rssi, GFP_KERNEL);
+}
+
+void wfx_event_handler_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif =
+		container_of(work, struct wfx_vif, event_handler_work);
+	struct wfx_hif_event *event;
+
+	LIST_HEAD(list);
+
+	spin_lock(&wvif->event_queue_lock);
+	list_splice_init(&wvif->event_queue, &list);
+	spin_unlock(&wvif->event_queue_lock);
+
+	list_for_each_entry(event, &list, link) {
+		switch (event->evt.event_id) {
+		case HIF_EVENT_IND_BSSLOST:
+			cancel_work_sync(&wvif->unjoin_work);
+			if (!down_trylock(&wvif->scan.lock)) {
+				wfx_cqm_bssloss_sm(wvif, 1, 0, 0);
+				up(&wvif->scan.lock);
+			} else {
+				/* Scan is in progress. Delay reporting.
+				 * Scan complete will trigger bss_loss_work
+				 */
+				wvif->delayed_link_loss = 1;
+				/* Also start a watchdog. */
+				schedule_delayed_work(&wvif->bss_loss_work, 5 * HZ);
+			}
+			break;
+		case HIF_EVENT_IND_BSSREGAINED:
+			wfx_cqm_bssloss_sm(wvif, 0, 0, 0);
+			cancel_work_sync(&wvif->unjoin_work);
+			break;
+		case HIF_EVENT_IND_RCPI_RSSI:
+			wfx_event_report_rssi(wvif, event->evt.event_data.rcpi_rssi);
+			break;
+		case HIF_EVENT_IND_PS_MODE_ERROR:
+			dev_warn(wvif->wdev->dev, "error while processing power save request\n");
+			break;
+		default:
+			dev_warn(wvif->wdev->dev, "unhandled event indication: %.2x\n", event->evt.event_id);
+			break;
+		}
+	}
+	__wfx_free_event_queue(&list);
+}
+
+void wfx_bss_loss_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif = container_of(work, struct wfx_vif, bss_loss_work.work);
+
+	ieee80211_connection_loss(wvif->vif);
+}
+
+void wfx_bss_params_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif = container_of(work, struct wfx_vif, bss_params_work);
+
+	mutex_lock(&wvif->wdev->conf_mutex);
+	wvif->bss_params.bss_flags.lost_count_only = 1;
+	hif_set_bss_params(wvif, &wvif->bss_params);
+	wvif->bss_params.bss_flags.lost_count_only = 0;
+	mutex_unlock(&wvif->wdev->conf_mutex);
+}
+
+void wfx_set_beacon_wakeup_period_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif = container_of(work, struct wfx_vif, set_beacon_wakeup_period_work);
+
+	hif_set_beacon_wakeup_period(wvif, wvif->dtim_period, wvif->dtim_period);
+}
+
+static void wfx_do_unjoin(struct wfx_vif *wvif)
+{
+	mutex_lock(&wvif->wdev->conf_mutex);
+
+	if (atomic_read(&wvif->scan.in_progress)) {
+		if (wvif->delayed_unjoin)
+			dev_dbg(wvif->wdev->dev, "delayed unjoin is already scheduled\n");
+		else
+			wvif->delayed_unjoin = true;
+		goto done;
+	}
+
+	wvif->delayed_link_loss = false;
+
+	if (!wvif->state)
+		goto done;
+
+	if (wvif->state == WFX_STATE_AP)
+		goto done;
+
+	cancel_work_sync(&wvif->update_filtering_work);
+	cancel_work_sync(&wvif->set_beacon_wakeup_period_work);
+	wvif->state = WFX_STATE_PASSIVE;
+
+	/* Unjoin is a reset. */
+	wfx_tx_flush(wvif->wdev);
+	hif_keep_alive_period(wvif, 0);
+	hif_reset(wvif, false);
+	hif_set_output_power(wvif, wvif->wdev->output_power * 10);
+	wvif->dtim_period = 0;
+	hif_set_macaddr(wvif, wvif->vif->addr);
+	wfx_free_event_queue(wvif);
+	cancel_work_sync(&wvif->event_handler_work);
+	wfx_cqm_bssloss_sm(wvif, 0, 0, 0);
+
+	/* Disable Block ACKs */
+	hif_set_block_ack_policy(wvif, 0, 0);
+
+	wvif->disable_beacon_filter = false;
+	wfx_update_filtering(wvif);
+	memset(&wvif->bss_params, 0, sizeof(wvif->bss_params));
+	wvif->setbssparams_done = false;
+	memset(&wvif->ht_info, 0, sizeof(wvif->ht_info));
+
+done:
+	mutex_unlock(&wvif->wdev->conf_mutex);
+}
+
+static void wfx_set_mfp(struct wfx_vif *wvif, struct cfg80211_bss *bss)
+{
+	const int pairwise_cipher_suite_count_offset = 8 / sizeof(uint16_t);
+	const int pairwise_cipher_suite_size = 4 / sizeof(uint16_t);
+	const int akm_suite_size = 4 / sizeof(uint16_t);
+	const uint16_t *ptr = NULL;
+	bool mfpc = false;
+	bool mfpr = false;
+
+	/* 802.11w protected mgmt frames */
+
+	/* retrieve MFPC and MFPR flags from beacon or PBRSP */
+
+	rcu_read_lock();
+	if (bss)
+		ptr = (const uint16_t *) ieee80211_bss_get_ie(bss, WLAN_EID_RSN);
+
+	if (ptr) {
+		ptr += pairwise_cipher_suite_count_offset;
+		ptr += 1 + pairwise_cipher_suite_size * *ptr;
+		ptr += 1 + akm_suite_size * *ptr;
+		mfpr = *ptr & BIT(6);
+		mfpc = *ptr & BIT(7);
+	}
+	rcu_read_unlock();
+
+	hif_set_mfp(wvif, mfpc, mfpr);
+}
+
+/* MUST be called with tx_lock held!  It will be unlocked for us. */
+static void wfx_do_join(struct wfx_vif *wvif)
+{
+	const u8 *bssid;
+	struct ieee80211_bss_conf *conf = &wvif->vif->bss_conf;
+	struct cfg80211_bss *bss = NULL;
+	struct hif_req_join join = {
+		.mode = conf->ibss_joined ? HIF_MODE_IBSS : HIF_MODE_BSS,
+		.preamble_type = conf->use_short_preamble ? HIF_PREAMBLE_SHORT : HIF_PREAMBLE_LONG,
+		.probe_for_join = 1,
+		.atim_window = 0,
+		.basic_rate_set = wfx_rate_mask_to_hw(wvif->wdev, conf->basic_rates),
+	};
+
+	if (wvif->channel->flags & IEEE80211_CHAN_NO_IR)
+		join.probe_for_join = 0;
+
+	if (wvif->state)
+		wfx_do_unjoin(wvif);
+
+	bssid = wvif->vif->bss_conf.bssid;
+
+	bss = cfg80211_get_bss(wvif->wdev->hw->wiphy, wvif->channel, bssid, NULL, 0,
+			       IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY);
+
+	if (!bss && !conf->ibss_joined) {
+		wfx_tx_unlock(wvif->wdev);
+		return;
+	}
+
+	mutex_lock(&wvif->wdev->conf_mutex);
+
+	/* Under the conf lock: check scan status and
+	 * bail out if it is in progress.
+	 */
+	if (atomic_read(&wvif->scan.in_progress)) {
+		wfx_tx_unlock(wvif->wdev);
+		goto done_put;
+	}
+
+	/* Sanity check basic rates */
+	if (!join.basic_rate_set)
+		join.basic_rate_set = 7;
+
+	/* Sanity check beacon interval */
+	if (!wvif->beacon_int)
+		wvif->beacon_int = 1;
+
+	join.beacon_interval = wvif->beacon_int;
+
+	// DTIM period will be set on first Beacon
+	wvif->dtim_period = 0;
+
+	join.channel_number = wvif->channel->hw_value;
+	memcpy(join.bssid, bssid, sizeof(join.bssid));
+
+	if (!conf->ibss_joined) {
+		const u8 *ssidie;
+
+		rcu_read_lock();
+		ssidie = ieee80211_bss_get_ie(bss, WLAN_EID_SSID);
+		if (ssidie) {
+			join.ssid_length = ssidie[1];
+			memcpy(join.ssid, &ssidie[2], join.ssid_length);
+		}
+		rcu_read_unlock();
+	}
+
+	wfx_tx_flush(wvif->wdev);
+
+	if (wvif_count(wvif->wdev) <= 1)
+		hif_set_block_ack_policy(wvif, 0xFF, 0xFF);
+
+	wfx_set_mfp(wvif, bss);
+
+	/* Perform actual join */
+	wvif->wdev->tx_burst_idx = -1;
+	if (hif_join(wvif, &join)) {
+		ieee80211_connection_loss(wvif->vif);
+		wvif->join_complete_status = -1;
+		/* Tx lock still held, unjoin will clear it. */
+		if (!schedule_work(&wvif->unjoin_work))
+			wfx_tx_unlock(wvif->wdev);
+	} else {
+		wvif->join_complete_status = 0;
+		if (wvif->vif->type == NL80211_IFTYPE_ADHOC)
+			wvif->state = WFX_STATE_IBSS;
+		else
+			wvif->state = WFX_STATE_PRE_STA;
+		wfx_tx_unlock(wvif->wdev);
+
+		/* Upload keys */
+		wfx_upload_keys(wvif);
+
+		/* Due to beacon filtering it is possible that the
+		 * AP's beacon is not known for the mac80211 stack.
+		 * Disable filtering temporary to make sure the stack
+		 * receives at least one
+		 */
+		wvif->disable_beacon_filter = true;
+	}
+	wfx_update_filtering(wvif);
+
+done_put:
+	mutex_unlock(&wvif->wdev->conf_mutex);
+	if (bss)
+		cfg80211_put_bss(wvif->wdev->hw->wiphy, bss);
+}
+
+void wfx_unjoin_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif = container_of(work, struct wfx_vif, unjoin_work);
+
+	wfx_do_unjoin(wvif);
+	wfx_tx_unlock(wvif->wdev);
+}
+
+int wfx_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		struct ieee80211_sta *sta)
+{
+	struct wfx_dev *wdev = hw->priv;
+	struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+	struct wfx_sta_priv *sta_priv = (struct wfx_sta_priv *) &sta->drv_priv;
+	struct wfx_link_entry *entry;
+	struct sk_buff *skb;
+
+	if (wvif->vif->type != NL80211_IFTYPE_AP)
+		return 0;
+
+	sta_priv->vif_id = wvif->id;
+	sta_priv->link_id = wfx_find_link_id(wvif, sta->addr);
+	if (!sta_priv->link_id) {
+		dev_warn(wdev->dev, "mo more link-id available\n");
+		return -ENOENT;
+	}
+
+	entry = &wvif->link_id_db[sta_priv->link_id - 1];
+	spin_lock_bh(&wvif->ps_state_lock);
+	if ((sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK) ==
+					IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK)
+		wvif->sta_asleep_mask |= BIT(sta_priv->link_id);
+	entry->status = WFX_LINK_HARD;
+	while ((skb = skb_dequeue(&entry->rx_queue)))
+		ieee80211_rx_irqsafe(wdev->hw, skb);
+	spin_unlock_bh(&wvif->ps_state_lock);
+	return 0;
+}
+
+int wfx_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		   struct ieee80211_sta *sta)
+{
+	struct wfx_dev *wdev = hw->priv;
+	struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+	struct wfx_sta_priv *sta_priv = (struct wfx_sta_priv *) &sta->drv_priv;
+	struct wfx_link_entry *entry;
+
+	if (wvif->vif->type != NL80211_IFTYPE_AP || !sta_priv->link_id)
+		return 0;
+
+	entry = &wvif->link_id_db[sta_priv->link_id - 1];
+	spin_lock_bh(&wvif->ps_state_lock);
+	entry->status = WFX_LINK_RESERVE;
+	entry->timestamp = jiffies;
+	wfx_tx_lock(wdev);
+	if (!schedule_work(&wvif->link_id_work))
+		wfx_tx_unlock(wdev);
+	spin_unlock_bh(&wvif->ps_state_lock);
+	flush_work(&wvif->link_id_work);
+	return 0;
+}
+
+void wfx_set_cts_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif = container_of(work, struct wfx_vif, set_cts_work);
+	u8 erp_ie[3] = { WLAN_EID_ERP_INFO, 1, 0 };
+	struct hif_ie_flags target_frame = {
+		.beacon = 1,
+	};
+
+	mutex_lock(&wvif->wdev->conf_mutex);
+	erp_ie[2] = wvif->erp_info;
+	mutex_unlock(&wvif->wdev->conf_mutex);
+
+	hif_erp_use_protection(wvif, erp_ie[2] & WLAN_ERP_USE_PROTECTION);
+
+	if (wvif->vif->type != NL80211_IFTYPE_STATION)
+		hif_update_ie(wvif, &target_frame, erp_ie, sizeof(erp_ie));
+}
+
+static int wfx_start_ap(struct wfx_vif *wvif)
+{
+	int ret;
+	struct ieee80211_bss_conf *conf = &wvif->vif->bss_conf;
+	struct hif_req_start start = {
+		.channel_number = wvif->channel->hw_value,
+		.beacon_interval = conf->beacon_int,
+		.dtim_period = conf->dtim_period,
+		.preamble_type = conf->use_short_preamble ? HIF_PREAMBLE_SHORT : HIF_PREAMBLE_LONG,
+		.basic_rate_set = wfx_rate_mask_to_hw(wvif->wdev, conf->basic_rates),
+	};
+
+	memset(start.ssid, 0, sizeof(start.ssid));
+	if (!conf->hidden_ssid) {
+		start.ssid_length = conf->ssid_len;
+		memcpy(start.ssid, conf->ssid, start.ssid_length);
+	}
+
+	wvif->beacon_int = conf->beacon_int;
+	wvif->dtim_period = conf->dtim_period;
+
+	memset(&wvif->link_id_db, 0, sizeof(wvif->link_id_db));
+
+	wvif->wdev->tx_burst_idx = -1;
+	ret = hif_start(wvif, &start);
+	if (!ret)
+		ret = wfx_upload_keys(wvif);
+	if (!ret) {
+		if (wvif_count(wvif->wdev) <= 1)
+			hif_set_block_ack_policy(wvif, 0xFF, 0xFF);
+		wvif->state = WFX_STATE_AP;
+		wfx_update_filtering(wvif);
+	}
+	return ret;
+}
+
+static int wfx_update_beaconing(struct wfx_vif *wvif)
+{
+	struct ieee80211_bss_conf *conf = &wvif->vif->bss_conf;
+
+	if (wvif->vif->type == NL80211_IFTYPE_AP) {
+		/* TODO: check if changed channel, band */
+		if (wvif->state != WFX_STATE_AP ||
+		    wvif->beacon_int != conf->beacon_int) {
+			wfx_tx_lock_flush(wvif->wdev);
+			if (wvif->state != WFX_STATE_PASSIVE)
+				hif_reset(wvif, false);
+			wvif->state = WFX_STATE_PASSIVE;
+			wfx_start_ap(wvif);
+			wfx_tx_unlock(wvif->wdev);
+		} else {
+		}
+	}
+	return 0;
+}
+
+static int wfx_upload_beacon(struct wfx_vif *wvif)
+{
+	int ret = 0;
+	struct sk_buff *skb = NULL;
+	struct ieee80211_mgmt *mgmt;
+	struct hif_mib_template_frame *p;
+
+	if (wvif->vif->type == NL80211_IFTYPE_STATION ||
+	    wvif->vif->type == NL80211_IFTYPE_MONITOR ||
+	    wvif->vif->type == NL80211_IFTYPE_UNSPECIFIED)
+		goto done;
+
+	skb = ieee80211_beacon_get(wvif->wdev->hw, wvif->vif);
+
+	if (!skb)
+		return -ENOMEM;
+
+	p = (struct hif_mib_template_frame *) skb_push(skb, 4);
+	p->frame_type = HIF_TMPLT_BCN;
+	p->init_rate = API_RATE_INDEX_B_1MBPS; /* 1Mbps DSSS */
+	p->frame_length = cpu_to_le16(skb->len - 4);
+
+	ret = hif_set_template_frame(wvif, p);
+
+	skb_pull(skb, 4);
+
+	if (ret)
+		goto done;
+	/* TODO: Distill probe resp; remove TIM and any other beacon-specific
+	 * IEs
+	 */
+	mgmt = (void *)skb->data;
+	mgmt->frame_control =
+		cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_PROBE_RESP);
+
+	p->frame_type = HIF_TMPLT_PRBRES;
+
+	ret = hif_set_template_frame(wvif, p);
+	wfx_fwd_probe_req(wvif, false);
+
+done:
+	if (!skb)
+		dev_kfree_skb(skb);
+	return ret;
+}
+
+static int wfx_is_ht(const struct wfx_ht_info *ht_info)
+{
+	return ht_info->channel_type != NL80211_CHAN_NO_HT;
+}
+
+static int wfx_ht_greenfield(const struct wfx_ht_info *ht_info)
+{
+	return wfx_is_ht(ht_info) &&
+		(ht_info->ht_cap.cap & IEEE80211_HT_CAP_GRN_FLD) &&
+		!(ht_info->operation_mode &
+		  IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT);
+}
+
+static int wfx_ht_ampdu_density(const struct wfx_ht_info *ht_info)
+{
+	if (!wfx_is_ht(ht_info))
+		return 0;
+	return ht_info->ht_cap.ampdu_density;
+}
+
+static void wfx_join_finalize(struct wfx_vif *wvif, struct ieee80211_bss_conf *info)
+{
+	struct ieee80211_sta *sta = NULL;
+	struct hif_mib_set_association_mode association_mode = { };
+
+	if (info->dtim_period)
+		wvif->dtim_period = info->dtim_period;
+	wvif->beacon_int = info->beacon_int;
+
+	rcu_read_lock();
+	if (info->bssid && !info->ibss_joined)
+		sta = ieee80211_find_sta(wvif->vif, info->bssid);
+	if (sta) {
+		wvif->ht_info.ht_cap = sta->ht_cap;
+		wvif->bss_params.operational_rate_set =
+			wfx_rate_mask_to_hw(wvif->wdev, sta->supp_rates[wvif->channel->band]);
+		wvif->ht_info.operation_mode = info->ht_operation_mode;
+	} else {
+		memset(&wvif->ht_info, 0, sizeof(wvif->ht_info));
+		wvif->bss_params.operational_rate_set = -1;
+	}
+	rcu_read_unlock();
+
+	/* Non Greenfield stations present */
+	if (wvif->ht_info.operation_mode & IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT)
+		hif_dual_cts_protection(wvif, true);
+	else
+		hif_dual_cts_protection(wvif, false);
+
+	association_mode.preambtype_use = 1;
+	association_mode.mode = 1;
+	association_mode.rateset = 1;
+	association_mode.spacing = 1;
+	association_mode.preamble_type = info->use_short_preamble ? HIF_PREAMBLE_SHORT : HIF_PREAMBLE_LONG;
+	association_mode.basic_rate_set = cpu_to_le32(wfx_rate_mask_to_hw(wvif->wdev, info->basic_rates));
+	association_mode.mixed_or_greenfield_type = wfx_ht_greenfield(&wvif->ht_info);
+	association_mode.mpdu_start_spacing = wfx_ht_ampdu_density(&wvif->ht_info);
+
+	wfx_cqm_bssloss_sm(wvif, 0, 0, 0);
+	cancel_work_sync(&wvif->unjoin_work);
+
+	wvif->bss_params.beacon_lost_count = 20;
+	wvif->bss_params.aid = info->aid;
+
+	if (wvif->dtim_period < 1)
+		wvif->dtim_period = 1;
+
+	hif_set_association_mode(wvif, &association_mode);
+
+	if (!info->ibss_joined) {
+		hif_keep_alive_period(wvif, 30 /* sec */);
+		hif_set_bss_params(wvif, &wvif->bss_params);
+		wvif->setbssparams_done = true;
+		wfx_set_beacon_wakeup_period_work(&wvif->set_beacon_wakeup_period_work);
+		wfx_set_pm(wvif, &wvif->powersave_mode);
+	}
+}
+
+void wfx_bss_info_changed(struct ieee80211_hw *hw,
+			     struct ieee80211_vif *vif,
+			     struct ieee80211_bss_conf *info,
+			     u32 changed)
+{
+	struct wfx_dev *wdev = hw->priv;
+	struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+	bool do_join = false;
+	int i;
+	int nb_arp_addr;
+
+	mutex_lock(&wdev->conf_mutex);
+
+	/* TODO: BSS_CHANGED_QOS */
+	if (changed & BSS_CHANGED_ARP_FILTER) {
+		struct hif_mib_arp_ip_addr_table filter = { };
+
+		nb_arp_addr = info->arp_addr_cnt;
+		if (nb_arp_addr <= 0 || nb_arp_addr > HIF_MAX_ARP_IP_ADDRTABLE_ENTRIES)
+			nb_arp_addr = 0;
+
+		for (i = 0; i < HIF_MAX_ARP_IP_ADDRTABLE_ENTRIES; i++) {
+			filter.condition_idx = i;
+			if (i < nb_arp_addr) {
+				// Caution: type of arp_addr_list[i] is __be32
+				memcpy(filter.ipv4_address, &info->arp_addr_list[i], sizeof(filter.ipv4_address));
+				filter.arp_enable = HIF_ARP_NS_FILTERING_ENABLE;
+			} else {
+				filter.arp_enable = HIF_ARP_NS_FILTERING_DISABLE;
+			}
+			hif_set_arp_ipv4_filter(wvif, &filter);
+		}
+	}
+
+	if (changed &
+	    (BSS_CHANGED_BEACON | BSS_CHANGED_AP_PROBE_RESP |
+	     BSS_CHANGED_BSSID | BSS_CHANGED_SSID | BSS_CHANGED_IBSS)) {
+		wvif->beacon_int = info->beacon_int;
+		wfx_update_beaconing(wvif);
+		wfx_upload_beacon(wvif);
+	}
+
+	if (changed & BSS_CHANGED_BEACON_ENABLED && wvif->state != WFX_STATE_IBSS) {
+		if (wvif->enable_beacon != info->enable_beacon) {
+			hif_beacon_transmit(wvif, info->enable_beacon);
+			wvif->enable_beacon = info->enable_beacon;
+		}
+	}
+
+	/* assoc/disassoc, or maybe AID changed */
+	if (changed & BSS_CHANGED_ASSOC) {
+		wfx_tx_lock_flush(wdev);
+		wvif->wep_default_key_id = -1;
+		wfx_tx_unlock(wdev);
+	}
+
+	if (changed & BSS_CHANGED_ASSOC && !info->assoc &&
+	    (wvif->state == WFX_STATE_STA || wvif->state == WFX_STATE_IBSS)) {
+		/* Shedule unjoin work */
+		wfx_tx_lock(wdev);
+		if (!schedule_work(&wvif->unjoin_work))
+			wfx_tx_unlock(wdev);
+	} else {
+		if (changed & BSS_CHANGED_BEACON_INT) {
+			if (info->ibss_joined)
+				do_join = true;
+			else if (wvif->state == WFX_STATE_AP)
+				wfx_update_beaconing(wvif);
+		}
+
+		if (changed & BSS_CHANGED_BSSID)
+			do_join = true;
+
+		if (changed &
+		    (BSS_CHANGED_ASSOC | BSS_CHANGED_BSSID |
+		     BSS_CHANGED_IBSS | BSS_CHANGED_BASIC_RATES | BSS_CHANGED_HT)) {
+			if (info->assoc) {
+				if (wvif->state < WFX_STATE_PRE_STA) {
+					ieee80211_connection_loss(vif);
+					mutex_unlock(&wdev->conf_mutex);
+					return;
+				} else if (wvif->state == WFX_STATE_PRE_STA) {
+					wvif->state = WFX_STATE_STA;
+				}
+			} else {
+				do_join = true;
+			}
+
+			if (info->assoc || info->ibss_joined)
+				wfx_join_finalize(wvif, info);
+			else
+				memset(&wvif->bss_params, 0, sizeof(wvif->bss_params));
+		}
+	}
+
+	/* ERP Protection */
+	if (changed & (BSS_CHANGED_ASSOC |
+		       BSS_CHANGED_ERP_CTS_PROT |
+		       BSS_CHANGED_ERP_PREAMBLE)) {
+		u32 prev_erp_info = wvif->erp_info;
+
+		if (info->use_cts_prot)
+			wvif->erp_info |= WLAN_ERP_USE_PROTECTION;
+		else if (!(prev_erp_info & WLAN_ERP_NON_ERP_PRESENT))
+			wvif->erp_info &= ~WLAN_ERP_USE_PROTECTION;
+
+		if (info->use_short_preamble)
+			wvif->erp_info |= WLAN_ERP_BARKER_PREAMBLE;
+		else
+			wvif->erp_info &= ~WLAN_ERP_BARKER_PREAMBLE;
+
+		if (prev_erp_info != wvif->erp_info)
+			schedule_work(&wvif->set_cts_work);
+	}
+
+	if (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_ERP_SLOT))
+		hif_slot_time(wvif, info->use_short_slot ? 9 : 20);
+
+	if (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_CQM)) {
+		struct hif_mib_rcpi_rssi_threshold th = {
+			.rolling_average_count = 8,
+			.detection = 1,
+		};
+
+		wvif->cqm_rssi_thold = info->cqm_rssi_thold;
+
+		if (!info->cqm_rssi_thold && !info->cqm_rssi_hyst) {
+			th.upperthresh = 1;
+			th.lowerthresh = 1;
+		} else {
+			/* FIXME It's not a correct way of setting threshold.
+			 * Upper and lower must be set equal here and adjusted
+			 * in callback. However current implementation is much
+			 * more reliable and stable.
+			 */
+			/* RSSI: signed Q8.0, RCPI: unsigned Q7.1
+			 * RSSI = RCPI / 2 - 110
+			 */
+			th.upper_threshold = info->cqm_rssi_thold + info->cqm_rssi_hyst;
+			th.upper_threshold = (th.upper_threshold + 110) * 2;
+			th.lower_threshold = info->cqm_rssi_thold;
+			th.lower_threshold = (th.lower_threshold + 110) * 2;
+		}
+		hif_set_rcpi_rssi_threshold(wvif, &th);
+	}
+
+	if (changed & BSS_CHANGED_TXPOWER && info->txpower != wdev->output_power) {
+		wdev->output_power = info->txpower;
+		hif_set_output_power(wvif, wdev->output_power * 10);
+	}
+	mutex_unlock(&wdev->conf_mutex);
+
+	if (do_join) {
+		wfx_tx_lock_flush(wdev);
+		wfx_do_join(wvif); /* Will unlock it for us */
+	}
+}
+
+static void wfx_ps_notify(struct wfx_vif *wvif, enum sta_notify_cmd notify_cmd,
+			  int link_id)
+{
+	u32 bit, prev;
+
+	spin_lock_bh(&wvif->ps_state_lock);
+	/* Zero link id means "for all link IDs" */
+	if (link_id) {
+		bit = BIT(link_id);
+	} else if (notify_cmd != STA_NOTIFY_AWAKE) {
+		dev_warn(wvif->wdev->dev, "unsupported notify command\n");
+		bit = 0;
+	} else {
+		bit = wvif->link_id_map;
+	}
+	prev = wvif->sta_asleep_mask & bit;
+
+	switch (notify_cmd) {
+	case STA_NOTIFY_SLEEP:
+		if (!prev) {
+			if (wvif->mcast_buffered && !wvif->sta_asleep_mask)
+				schedule_work(&wvif->mcast_start_work);
+			wvif->sta_asleep_mask |= bit;
+		}
+		break;
+	case STA_NOTIFY_AWAKE:
+		if (prev) {
+			wvif->sta_asleep_mask &= ~bit;
+			wvif->pspoll_mask &= ~bit;
+			if (link_id && !wvif->sta_asleep_mask)
+				schedule_work(&wvif->mcast_stop_work);
+			wfx_bh_request_tx(wvif->wdev);
+		}
+		break;
+	}
+	spin_unlock_bh(&wvif->ps_state_lock);
+}
+
+void wfx_sta_notify(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		    enum sta_notify_cmd notify_cmd, struct ieee80211_sta *sta)
+{
+	struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+	struct wfx_sta_priv *sta_priv = (struct wfx_sta_priv *) &sta->drv_priv;
+
+	wfx_ps_notify(wvif, notify_cmd, sta_priv->link_id);
+}
+
 static int wfx_set_tim_impl(struct wfx_vif *wvif, bool aid0_bit_set)
 {
 	struct sk_buff *skb;
@@ -33,8 +1208,11 @@ static int wfx_set_tim_impl(struct wfx_vif *wvif, bool aid0_bit_set)
 
 	skb = ieee80211_beacon_get_tim(wvif->wdev->hw, wvif->vif,
 				       &tim_offset, &tim_length);
-	if (!skb)
+	if (!skb) {
+		if (!__wfx_flush(wvif->wdev, true))
+			wfx_tx_unlock(wvif->wdev);
 		return -ENOENT;
+	}
 	tim_ptr = skb->data + tim_offset;
 
 	if (tim_offset && tim_length >= 6) {
@@ -56,16 +1234,34 @@ static int wfx_set_tim_impl(struct wfx_vif *wvif, bool aid0_bit_set)
 	return 0;
 }
 
+void wfx_set_tim_work(struct work_struct *work)
+{
+	struct wfx_vif *wvif = container_of(work, struct wfx_vif, set_tim_work);
+
+	wfx_set_tim_impl(wvif, wvif->aid0_bit_set);
+}
+
+int wfx_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta, bool set)
+{
+	struct wfx_dev *wdev = hw->priv;
+	struct wfx_sta_priv *sta_dev = (struct wfx_sta_priv *) &sta->drv_priv;
+	struct wfx_vif *wvif = wdev_to_wvif(wdev, sta_dev->vif_id);
+
+	schedule_work(&wvif->set_tim_work);
+	return 0;
+}
+
 static void wfx_mcast_start_work(struct work_struct *work)
 {
 	struct wfx_vif *wvif = container_of(work, struct wfx_vif, mcast_start_work);
+	long tmo = wvif->dtim_period * TU_TO_JIFFIES(wvif->beacon_int + 20);
 
 	cancel_work_sync(&wvif->mcast_stop_work);
 	if (!wvif->aid0_bit_set) {
 		wfx_tx_lock_flush(wvif->wdev);
 		wfx_set_tim_impl(wvif, true);
 		wvif->aid0_bit_set = true;
-		mod_timer(&wvif->mcast_timeout, TU_TO_JIFFIES(1000));
+		mod_timer(&wvif->mcast_timeout, jiffies + tmo);
 		wfx_tx_unlock(wvif->wdev);
 	}
 }
@@ -95,6 +1291,134 @@ static void wfx_mcast_timeout(struct timer_list *t)
 	spin_unlock_bh(&wvif->ps_state_lock);
 }
 
+int wfx_ampdu_action(struct ieee80211_hw *hw,
+		     struct ieee80211_vif *vif,
+		     struct ieee80211_ampdu_params *params)
+{
+	/* Aggregation is implemented fully in firmware,
+	 * including block ack negotiation. Do not allow
+	 * mac80211 stack to do anything: it interferes with
+	 * the firmware.
+	 */
+
+	/* Note that we still need this function stubbed. */
+
+	return -ENOTSUPP;
+}
+
+void wfx_suspend_resume(struct wfx_vif *wvif,
+			struct hif_ind_suspend_resume_tx *arg)
+{
+	if (arg->suspend_resume_flags.bc_mc_only) {
+		bool cancel_tmo = false;
+
+		spin_lock_bh(&wvif->ps_state_lock);
+		if (!arg->suspend_resume_flags.resume)
+			wvif->mcast_tx = false;
+		else
+			wvif->mcast_tx = wvif->aid0_bit_set && wvif->mcast_buffered;
+		if (wvif->mcast_tx) {
+			cancel_tmo = true;
+			wfx_bh_request_tx(wvif->wdev);
+		}
+		spin_unlock_bh(&wvif->ps_state_lock);
+		if (cancel_tmo)
+			del_timer_sync(&wvif->mcast_timeout);
+	} else if (arg->suspend_resume_flags.resume) {
+		// FIXME: should change each station status independently
+		wfx_ps_notify(wvif, STA_NOTIFY_AWAKE, 0);
+		wfx_bh_request_tx(wvif->wdev);
+	} else {
+		// FIXME: should change each station status independently
+		wfx_ps_notify(wvif, STA_NOTIFY_SLEEP, 0);
+	}
+}
+
+int wfx_add_chanctx(struct ieee80211_hw *hw,
+		    struct ieee80211_chanctx_conf *conf)
+{
+	return 0;
+}
+
+void wfx_remove_chanctx(struct ieee80211_hw *hw,
+			struct ieee80211_chanctx_conf *conf)
+{
+}
+
+void wfx_change_chanctx(struct ieee80211_hw *hw,
+			struct ieee80211_chanctx_conf *conf,
+			u32 changed)
+{
+}
+
+int wfx_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+			   struct ieee80211_chanctx_conf *conf)
+{
+	struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+	struct ieee80211_channel *ch = conf->def.chan;
+
+	WARN(wvif->channel, "channel overwrite");
+	wvif->channel = ch;
+	wvif->ht_info.channel_type = cfg80211_get_chandef_type(&conf->def);
+
+	return 0;
+}
+
+void wfx_unassign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+			      struct ieee80211_chanctx_conf *conf)
+{
+	struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+	struct ieee80211_channel *ch = conf->def.chan;
+
+	WARN(wvif->channel != ch, "channel mismatch");
+	wvif->channel = NULL;
+}
+
+int wfx_config(struct ieee80211_hw *hw, u32 changed)
+{
+	int ret = 0;
+	struct wfx_dev *wdev = hw->priv;
+	struct ieee80211_conf *conf = &hw->conf;
+	struct wfx_vif *wvif;
+
+	// FIXME: Interface id should not been hardcoded
+	wvif = wdev_to_wvif(wdev, 0);
+	if (!wvif) {
+		WARN(1, "interface 0 does not exist anymore");
+		return 0;
+	}
+
+	down(&wvif->scan.lock);
+	mutex_lock(&wdev->conf_mutex);
+	if (changed & IEEE80211_CONF_CHANGE_POWER) {
+		wdev->output_power = conf->power_level;
+		hif_set_output_power(wvif, wdev->output_power * 10);
+	}
+
+	if (changed & IEEE80211_CONF_CHANGE_PS) {
+		wvif = NULL;
+		while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
+			memset(&wvif->powersave_mode, 0, sizeof(wvif->powersave_mode));
+			if (conf->flags & IEEE80211_CONF_PS) {
+				wvif->powersave_mode.pm_mode.enter_psm = 1;
+				if (conf->dynamic_ps_timeout > 0) {
+					wvif->powersave_mode.pm_mode.fast_psm = 1;
+					// Firmware does not support more than 128ms
+					wvif->powersave_mode.fast_psm_idle_period =
+						min(conf->dynamic_ps_timeout * 2, 255);
+				}
+			}
+			if (wvif->state == WFX_STATE_STA && wvif->bss_params.aid)
+				wfx_set_pm(wvif, &wvif->powersave_mode);
+		}
+		wvif = wdev_to_wvif(wdev, 0);
+	}
+
+	mutex_unlock(&wdev->conf_mutex);
+	up(&wvif->scan.lock);
+	return ret;
+}
+
 int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 {
 	int i;
@@ -138,8 +1462,22 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 		default_edca_params[IEEE80211_AC_BK].queue_id = HIF_QUEUE_ID_BESTEFFORT;
 	}
 
+	vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER |
+			     IEEE80211_VIF_SUPPORTS_UAPSD |
+			     IEEE80211_VIF_SUPPORTS_CQM_RSSI;
+
 	mutex_lock(&wdev->conf_mutex);
 
+	switch (vif->type) {
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_ADHOC:
+	case NL80211_IFTYPE_AP:
+		break;
+	default:
+		mutex_unlock(&wdev->conf_mutex);
+		return -EOPNOTSUPP;
+	}
+
 	for (i = 0; i < ARRAY_SIZE(wdev->vif); i++) {
 		if (!wdev->vif[i]) {
 			wdev->vif[i] = vif;
@@ -151,6 +1489,7 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 		mutex_unlock(&wdev->conf_mutex);
 		return -EOPNOTSUPP;
 	}
+	// FIXME: prefer use of container_of() to get vif
 	wvif->vif = vif;
 	wvif->wdev = wdev;
 
@@ -158,11 +1497,16 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 	INIT_DELAYED_WORK(&wvif->link_id_gc_work, wfx_link_id_gc_work);
 
 	spin_lock_init(&wvif->ps_state_lock);
+	INIT_WORK(&wvif->set_tim_work, wfx_set_tim_work);
 
 	INIT_WORK(&wvif->mcast_start_work, wfx_mcast_start_work);
 	INIT_WORK(&wvif->mcast_stop_work, wfx_mcast_stop_work);
 	timer_setup(&wvif->mcast_timeout, wfx_mcast_timeout, 0);
 
+	wvif->setbssparams_done = false;
+	mutex_init(&wvif->bss_loss_lock);
+	INIT_DELAYED_WORK(&wvif->bss_loss_work, wfx_bss_loss_work);
+
 	wvif->wep_default_key_id = -1;
 	INIT_WORK(&wvif->wep_key_work, wfx_wep_key_work);
 
@@ -170,22 +1514,115 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 	INIT_WORK(&wvif->scan.work, wfx_scan_work);
 	INIT_DELAYED_WORK(&wvif->scan.timeout, wfx_scan_timeout);
 
+	spin_lock_init(&wvif->event_queue_lock);
+	INIT_LIST_HEAD(&wvif->event_queue);
+	INIT_WORK(&wvif->event_handler_work, wfx_event_handler_work);
+
+	init_completion(&wvif->set_pm_mode_complete);
+	complete(&wvif->set_pm_mode_complete);
+	INIT_WORK(&wvif->set_beacon_wakeup_period_work, wfx_set_beacon_wakeup_period_work);
+	INIT_WORK(&wvif->update_filtering_work, wfx_update_filtering_work);
+	INIT_WORK(&wvif->bss_params_work, wfx_bss_params_work);
+	INIT_WORK(&wvif->set_cts_work, wfx_set_cts_work);
+	INIT_WORK(&wvif->unjoin_work, wfx_unjoin_work);
+
 	mutex_unlock(&wdev->conf_mutex);
+
+	hif_set_macaddr(wvif, vif->addr);
 	BUG_ON(ARRAY_SIZE(default_edca_params) != ARRAY_SIZE(wvif->edca.params));
-	for (i = 0; i < IEEE80211_NUM_ACS; i++)
+	for (i = 0; i < IEEE80211_NUM_ACS; i++) {
 		memcpy(&wvif->edca.params[i], &default_edca_params[i], sizeof(default_edca_params[i]));
+		wvif->edca.uapsd_enable[i] = false;
+		hif_set_edca_queue_params(wvif, &wvif->edca.params[i]);
+	}
+	wfx_set_uapsd_param(wvif, &wvif->edca);
+
 	tx_policy_init(wvif);
+	wvif = NULL;
+	while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
+		// Combo mode does not support Block Acks. We can re-enable them
+		if (wvif_count(wdev) == 1)
+			hif_set_block_ack_policy(wvif, 0xFF, 0xFF);
+		else
+			hif_set_block_ack_policy(wvif, 0x00, 0x00);
+		// Combo force powersave mode. We can re-enable it now
+		wfx_set_pm(wvif, &wvif->powersave_mode);
+	}
 	return 0;
 }
 
 void wfx_remove_interface(struct ieee80211_hw *hw,
 			  struct ieee80211_vif *vif)
 {
+	struct wfx_dev *wdev = hw->priv;
 	struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+	int i;
 
+	// If scan is in progress, stop it
+	while (down_trylock(&wvif->scan.lock))
+		schedule();
+	up(&wvif->scan.lock);
+	wait_for_completion_timeout(&wvif->set_pm_mode_complete, msecs_to_jiffies(300));
+
+	mutex_lock(&wdev->conf_mutex);
+	switch (wvif->state) {
+	case WFX_STATE_PRE_STA:
+	case WFX_STATE_STA:
+	case WFX_STATE_IBSS:
+		wfx_tx_lock_flush(wdev);
+		if (!schedule_work(&wvif->unjoin_work))
+			wfx_tx_unlock(wdev);
+		break;
+	case WFX_STATE_AP:
+		for (i = 0; wvif->link_id_map; ++i) {
+			if (wvif->link_id_map & BIT(i)) {
+				wfx_unmap_link(wvif, i);
+				wvif->link_id_map &= ~BIT(i);
+			}
+		}
+		memset(wvif->link_id_db, 0, sizeof(wvif->link_id_db));
+		wvif->sta_asleep_mask = 0;
+		wvif->enable_beacon = false;
+		wvif->mcast_tx = false;
+		wvif->aid0_bit_set = false;
+		wvif->mcast_buffered = false;
+		wvif->pspoll_mask = 0;
+		/* reset.link_id = 0; */
+		hif_reset(wvif, false);
+		break;
+	default:
+		break;
+	}
+
+	wvif->state = WFX_STATE_PASSIVE;
 	wfx_tx_queues_wait_empty_vif(wvif);
+	wfx_tx_unlock(wdev);
+
+	/* FIXME: In add to reset MAC address, try to reset interface */
+	hif_set_macaddr(wvif, NULL);
+
+	cancel_delayed_work_sync(&wvif->scan.timeout);
+
+	wfx_cqm_bssloss_sm(wvif, 0, 0, 0);
+	cancel_work_sync(&wvif->unjoin_work);
 	cancel_delayed_work_sync(&wvif->link_id_gc_work);
 	del_timer_sync(&wvif->mcast_timeout);
+	wfx_free_event_queue(wvif);
+
+	wdev->vif[wvif->id] = NULL;
+	wvif->vif = NULL;
+
+	mutex_unlock(&wdev->conf_mutex);
+	wvif = NULL;
+	while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
+		// Combo mode does not support Block Acks. We can re-enable them
+		if (wvif_count(wdev) == 1)
+			hif_set_block_ack_policy(wvif, 0xFF, 0xFF);
+		else
+			hif_set_block_ack_policy(wvif, 0x00, 0x00);
+		// Combo force powersave mode. We can re-enable it now
+		wfx_set_pm(wvif, &wvif->powersave_mode);
+	}
 }
 
 int wfx_start(struct ieee80211_hw *hw)
diff --git a/drivers/staging/wfx/sta.h b/drivers/staging/wfx/sta.h
index dd1b6b3fc2f1..307ed0196110 100644
--- a/drivers/staging/wfx/sta.h
+++ b/drivers/staging/wfx/sta.h
@@ -12,14 +12,40 @@
 
 #include "hif_api_cmd.h"
 
+struct wfx_dev;
 struct wfx_vif;
 
+enum wfx_state {
+	WFX_STATE_PASSIVE = 0,
+	WFX_STATE_PRE_STA,
+	WFX_STATE_STA,
+	WFX_STATE_IBSS,
+	WFX_STATE_AP,
+};
+
+struct wfx_ht_info {
+	struct ieee80211_sta_ht_cap ht_cap;
+	enum nl80211_channel_type channel_type;
+	uint16_t operation_mode;
+};
+
+struct wfx_hif_event {
+	struct list_head link;
+	struct hif_ind_event evt;
+};
+
 struct wfx_edca_params {
 	/* NOTE: index is a linux queue id. */
 	struct hif_req_edca_queue_params params[IEEE80211_NUM_ACS];
 	bool uapsd_enable[IEEE80211_NUM_ACS];
 };
 
+struct wfx_grp_addr_table {
+	bool enable;
+	int num_addresses;
+	u8 address_list[8][ETH_ALEN];
+};
+
 struct wfx_sta_priv {
 	int link_id;
 	int vif_id;
@@ -28,9 +54,48 @@ struct wfx_sta_priv {
 // mac80211 interface
 int wfx_start(struct ieee80211_hw *hw);
 void wfx_stop(struct ieee80211_hw *hw);
+int wfx_config(struct ieee80211_hw *hw, u32 changed);
+int wfx_set_rts_threshold(struct ieee80211_hw *hw, u32 value);
+u64 wfx_prepare_multicast(struct ieee80211_hw *hw,
+			  struct netdev_hw_addr_list *mc_list);
+void wfx_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags,
+			  unsigned int *total_flags, u64 unused);
+
 int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
 void wfx_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
+void wfx_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+	       u32 queues, bool drop);
+int wfx_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		u16 queue, const struct ieee80211_tx_queue_params *params);
+void wfx_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+			  struct ieee80211_bss_conf *info, u32 changed);
+int wfx_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		struct ieee80211_sta *sta);
+int wfx_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		   struct ieee80211_sta *sta);
+void wfx_sta_notify(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		    enum sta_notify_cmd cmd, struct ieee80211_sta *sta);
+int wfx_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta, bool set);
+int wfx_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		     struct ieee80211_ampdu_params *params);
+int wfx_add_chanctx(struct ieee80211_hw *hw,
+		    struct ieee80211_chanctx_conf *conf);
+void wfx_remove_chanctx(struct ieee80211_hw *hw,
+			struct ieee80211_chanctx_conf *conf);
+void wfx_change_chanctx(struct ieee80211_hw *hw,
+			struct ieee80211_chanctx_conf *conf, u32 changed);
+int wfx_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+			   struct ieee80211_chanctx_conf *conf);
+void wfx_unassign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+			      struct ieee80211_chanctx_conf *conf);
 
+// WSM Callbacks
+void wfx_suspend_resume(struct wfx_vif *wvif, struct hif_ind_suspend_resume_tx *arg);
+
+// Other Helpers
+void wfx_cqm_bssloss_sm(struct wfx_vif *wvif, int init, int good, int bad);
+void wfx_update_filtering(struct wfx_vif *wvif);
+int wfx_set_pm(struct wfx_vif *wvif, const struct hif_req_set_pm_mode *arg);
 int wfx_fwd_probe_req(struct wfx_vif *wvif, bool enable);
 
 #endif /* WFX_STA_H */
diff --git a/drivers/staging/wfx/wfx.h b/drivers/staging/wfx/wfx.h
index a86ddfaed825..489836837b0a 100644
--- a/drivers/staging/wfx/wfx.h
+++ b/drivers/staging/wfx/wfx.h
@@ -11,6 +11,8 @@
 #define WFX_H
 
 #include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
 #include <net/mac80211.h>
 
 #include "bh.h"
@@ -61,8 +63,15 @@ struct wfx_dev {
 struct wfx_vif {
 	struct wfx_dev		*wdev;
 	struct ieee80211_vif	*vif;
+	struct ieee80211_channel *channel;
 	int			id;
+	enum wfx_state		state;
 
+	int			delayed_link_loss;
+	int			bss_loss_state;
+	u32			bss_loss_confirm_id;
+	struct mutex		bss_loss_lock;
+	struct delayed_work	bss_loss_work;
 
 	u32			link_id_map;
 	struct wfx_link_entry	link_id_db[WFX_MAX_STA_IN_AP_MODE];
@@ -72,6 +81,7 @@ struct wfx_vif {
 	bool			aid0_bit_set;
 	bool			mcast_tx;
 	bool			mcast_buffered;
+	struct wfx_grp_addr_table mcast_filter;
 	struct timer_list	mcast_timeout;
 	struct work_struct	mcast_start_work;
 	struct work_struct	mcast_stop_work;
@@ -86,13 +96,40 @@ struct wfx_vif {
 	u32			sta_asleep_mask;
 	u32			pspoll_mask;
 	spinlock_t		ps_state_lock;
+	struct work_struct	set_tim_work;
+
+	int			dtim_period;
+	int			beacon_int;
+	bool			enable_beacon;
+	struct work_struct	set_beacon_wakeup_period_work;
 
 	bool			filter_bssid;
 	bool			fwd_probe_req;
+	bool			disable_beacon_filter;
+	struct work_struct	update_filtering_work;
 
+	u32			erp_info;
+	int			cqm_rssi_thold;
+	bool			setbssparams_done;
+	struct wfx_ht_info	ht_info;
 	struct wfx_edca_params	edca;
+	struct hif_mib_set_uapsd_information uapsd_info;
+	struct hif_req_set_bss_params bss_params;
+	struct work_struct	bss_params_work;
+	struct work_struct	set_cts_work;
+
+	int			join_complete_status;
+	bool			delayed_unjoin;
+	struct work_struct	unjoin_work;
 
 	struct wfx_scan		scan;
+
+	struct hif_req_set_pm_mode powersave_mode;
+	struct completion	set_pm_mode_complete;
+
+	struct list_head	event_queue;
+	spinlock_t		event_queue_lock;
+	struct work_struct	event_handler_work;
 };
 
 static inline struct wfx_vif *wdev_to_wvif(struct wfx_dev *wdev, int vif_id)
@@ -126,6 +163,20 @@ static inline struct wfx_vif *wvif_iterate(struct wfx_dev *wdev, struct wfx_vif
 	return NULL;
 }
 
+static inline int wvif_count(struct wfx_dev *wdev)
+{
+	int i;
+	int ret = 0;
+	struct wfx_vif *wvif;
+
+	for (i = 0; i < ARRAY_SIZE(wdev->vif); i++) {
+		wvif = wdev_to_wvif(wdev, i);
+		if (wvif)
+			ret++;
+	}
+	return ret;
+}
+
 static inline void memreverse(uint8_t *src, uint8_t length)
 {
 	uint8_t *lo = src;
-- 
2.20.1

^ permalink raw reply related

* [PATCH v2 15/20] staging: wfx: add debug files and trace debug events
From: Jerome Pouiller @ 2019-09-19 13:52 UTC (permalink / raw)
  To: devel@driverdev.osuosl.org, linux-wireless@vger.kernel.org
  Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	Greg Kroah-Hartman, Kalle Valo, David S . Miller, David Le Goff,
	Jerome Pouiller
In-Reply-To: <20190919135220.30663-1-Jerome.Pouiller@silabs.com>

From: Jérôme Pouiller <jerome.pouiller@silabs.com>

Add traces when debug events happen and allow to ask internal
information to chip.

These features work independently from mac80211.

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
 drivers/staging/wfx/debug.c  | 105 +++++++++++++++++++++++++++++++++++
 drivers/staging/wfx/hif_rx.c |  80 ++++++++++++++++++++++++++
 drivers/staging/wfx/main.c   |   2 +
 drivers/staging/wfx/wfx.h    |  16 ++++++
 4 files changed, 203 insertions(+)

diff --git a/drivers/staging/wfx/debug.c b/drivers/staging/wfx/debug.c
index 0619c7d1cf79..1e23bb5bde3e 100644
--- a/drivers/staging/wfx/debug.c
+++ b/drivers/staging/wfx/debug.c
@@ -6,11 +6,13 @@
  * Copyright (c) 2010, ST-Ericsson
  */
 #include <linux/debugfs.h>
+#include <linux/seq_file.h>
 #include <linux/crc32.h>
 
 #include "debug.h"
 #include "wfx.h"
 #include "main.h"
+#include "hif_tx_mib.h"
 
 #define CREATE_TRACE_POINTS
 #include "traces.h"
@@ -55,6 +57,107 @@ const char *get_reg_name(unsigned long id)
 	return get_symbol(id, wfx_reg_print_map);
 }
 
+static int wfx_counters_show(struct seq_file *seq, void *v)
+{
+	int ret;
+	struct wfx_dev *wdev = seq->private;
+	struct hif_mib_extended_count_table counters;
+
+	ret = hif_get_counters_table(wdev, &counters);
+	if (ret < 0)
+		return ret;
+	if (ret > 0)
+		return -EIO;
+
+#define PUT_COUNTER(name) \
+	seq_printf(seq, "%24s %d\n", #name ":", le32_to_cpu(counters.count_##name))
+
+	PUT_COUNTER(tx_packets);
+	PUT_COUNTER(tx_multicast_frames);
+	PUT_COUNTER(tx_frames_success);
+	PUT_COUNTER(tx_frame_failures);
+	PUT_COUNTER(tx_frames_retried);
+	PUT_COUNTER(tx_frames_multi_retried);
+
+	PUT_COUNTER(rts_success);
+	PUT_COUNTER(rts_failures);
+	PUT_COUNTER(ack_failures);
+
+	PUT_COUNTER(rx_packets);
+	PUT_COUNTER(rx_frames_success);
+	PUT_COUNTER(rx_packet_errors);
+	PUT_COUNTER(plcp_errors);
+	PUT_COUNTER(fcs_errors);
+	PUT_COUNTER(rx_decryption_failures);
+	PUT_COUNTER(rx_mic_failures);
+	PUT_COUNTER(rx_no_key_failures);
+	PUT_COUNTER(rx_frame_duplicates);
+	PUT_COUNTER(rx_multicast_frames);
+	PUT_COUNTER(rx_cmacicv_errors);
+	PUT_COUNTER(rx_cmac_replays);
+	PUT_COUNTER(rx_mgmt_ccmp_replays);
+
+	PUT_COUNTER(rx_beacon);
+	PUT_COUNTER(miss_beacon);
+
+#undef PUT_COUNTER
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(wfx_counters);
+
+static const char * const channel_names[] = {
+	[0] = "1M",
+	[1] = "2M",
+	[2] = "5.5M",
+	[3] = "11M",
+	/* Entries 4 and 5 does not exist */
+	[6] = "6M",
+	[7] = "9M",
+	[8] = "12M",
+	[9] = "18M",
+	[10] = "24M",
+	[11] = "36M",
+	[12] = "48M",
+	[13] = "54M",
+	[14] = "MCS0",
+	[15] = "MCS1",
+	[16] = "MCS2",
+	[17] = "MCS3",
+	[18] = "MCS4",
+	[19] = "MCS5",
+	[20] = "MCS6",
+	[21] = "MCS7",
+};
+
+static int wfx_rx_stats_show(struct seq_file *seq, void *v)
+{
+	struct wfx_dev *wdev = seq->private;
+	struct hif_rx_stats *st = &wdev->rx_stats;
+	int i;
+
+	mutex_lock(&wdev->rx_stats_lock);
+	seq_printf(seq, "Timestamp: %dus\n", st->date);
+	seq_printf(seq, "Low power clock: frequency %uHz, external %s\n",
+		st->pwr_clk_freq,
+		st->is_ext_pwr_clk ? "yes" : "no");
+	seq_printf(seq, "Num. of frames: %d, PER (x10e4): %d, Throughput: %dKbps/s\n",
+		st->nb_rx_frame, st->per_total, st->throughput);
+	seq_puts(seq, "       Num. of      PER     RSSI      SNR      CFO\n");
+	seq_puts(seq, "        frames  (x10e4)    (dBm)     (dB)    (kHz)\n");
+	for (i = 0; i < ARRAY_SIZE(channel_names); i++) {
+		if (channel_names[i])
+			seq_printf(seq, "%5s %8d %8d %8d %8d %8d\n",
+				   channel_names[i], st->nb_rx_by_rate[i],
+				   st->per[i], st->rssi[i] / 100,
+				   st->snr[i] / 100, st->cfo[i]);
+	}
+	mutex_unlock(&wdev->rx_stats_lock);
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(wfx_rx_stats);
+
 static ssize_t wfx_send_pds_write(struct file *file, const char __user *user_buf,
 			     size_t count, loff_t *ppos)
 {
@@ -190,6 +293,8 @@ int wfx_debug_init(struct wfx_dev *wdev)
 	struct dentry *d;
 
 	d = debugfs_create_dir("wfx", wdev->hw->wiphy->debugfsdir);
+	debugfs_create_file("counters", 0444, d, wdev, &wfx_counters_fops);
+	debugfs_create_file("rx_stats", 0444, d, wdev, &wfx_rx_stats_fops);
 	debugfs_create_file("send_pds", 0200, d, wdev, &wfx_send_pds_fops);
 	debugfs_create_file("burn_slk_key", 0200, d, wdev, &wfx_burn_slk_key_fops);
 	debugfs_create_file("send_hif_msg", 0600, d, wdev, &wfx_send_hif_msg_fops);
diff --git a/drivers/staging/wfx/hif_rx.c b/drivers/staging/wfx/hif_rx.c
index 6b9683d69a3f..c93bae1b6acf 100644
--- a/drivers/staging/wfx/hif_rx.c
+++ b/drivers/staging/wfx/hif_rx.c
@@ -94,13 +94,93 @@ static int hif_keys_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *
 	return 0;
 }
 
+static int hif_join_complete_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
+{
+	struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
+
+	WARN_ON(!wvif);
+	dev_warn(wdev->dev, "unattended JoinCompleteInd\n");
+
+	return 0;
+}
+
+static int hif_error_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
+{
+	struct hif_ind_error *body = buf;
+	u8 *pRollback = (u8 *) body->data;
+	u32 *pStatus = (u32 *) body->data;
+
+	switch (body->type) {
+	case HIF_ERROR_FIRMWARE_ROLLBACK:
+		dev_err(wdev->dev, "asynchronous error: firmware rollback error %d\n", *pRollback);
+		break;
+	case HIF_ERROR_FIRMWARE_DEBUG_ENABLED:
+		dev_err(wdev->dev, "asynchronous error: firmware debug feature enabled\n");
+		break;
+	case HIF_ERROR_OUTDATED_SESSION_KEY:
+		dev_err(wdev->dev, "asynchronous error: secure link outdated key: %#.8x\n", *pStatus);
+		break;
+	case HIF_ERROR_INVALID_SESSION_KEY:
+		dev_err(wdev->dev, "asynchronous error: invalid session key\n");
+		break;
+	case HIF_ERROR_OOR_VOLTAGE:
+		dev_err(wdev->dev, "asynchronous error: out-of-range overvoltage: %#.8x\n", *pStatus);
+		break;
+	case HIF_ERROR_PDS_VERSION:
+		dev_err(wdev->dev, "asynchronous error: wrong PDS payload or version: %#.8x\n", *pStatus);
+		break;
+	default:
+		dev_err(wdev->dev, "asynchronous error: unknown (%d)\n", body->type);
+		break;
+	}
+	return 0;
+}
+
+static int hif_generic_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
+{
+	struct hif_ind_generic *body = buf;
+
+	switch (body->indication_type) {
+	case HIF_GENERIC_INDICATION_TYPE_RAW:
+		return 0;
+	case HIF_GENERIC_INDICATION_TYPE_STRING:
+		dev_info(wdev->dev, "firmware says: %s\n", (char *) body->indication_data.raw_data);
+		return 0;
+	case HIF_GENERIC_INDICATION_TYPE_RX_STATS:
+		mutex_lock(&wdev->rx_stats_lock);
+		// Older firmware send a generic indication beside RxStats
+		if (!wfx_api_older_than(wdev, 1, 4))
+			dev_info(wdev->dev, "Rx test ongoing. Temperature: %d°C\n", body->indication_data.rx_stats.current_temp);
+		memcpy(&wdev->rx_stats, &body->indication_data.rx_stats, sizeof(wdev->rx_stats));
+		mutex_unlock(&wdev->rx_stats_lock);
+		return 0;
+	default:
+		dev_err(wdev->dev, "generic_indication: unknown indication type: %#.8x\n", body->indication_type);
+		return -EIO;
+	}
+}
+
+static int hif_exception_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
+{
+	size_t len = hif->len - 4; // drop header
+	dev_err(wdev->dev, "firmware exception\n");
+	print_hex_dump_bytes("Dump: ", DUMP_PREFIX_NONE, buf, len);
+	wdev->chip_frozen = 1;
+
+	return -1;
+}
+
 static const struct {
 	int msg_id;
 	int (*handler)(struct wfx_dev *wdev, struct hif_msg *hif, void *buf);
 } hif_handlers[] = {
 	{ HIF_IND_ID_STARTUP,              hif_startup_indication },
 	{ HIF_IND_ID_WAKEUP,               hif_wakeup_indication },
+	{ HIF_IND_ID_JOIN_COMPLETE,        hif_join_complete_indication },
 	{ HIF_IND_ID_SL_EXCHANGE_PUB_KEYS, hif_keys_indication },
+	{ HIF_IND_ID_GENERIC,              hif_generic_indication },
+	{ HIF_IND_ID_ERROR,                hif_error_indication },
+	{ HIF_IND_ID_EXCEPTION,            hif_exception_indication },
 };
 
 void wfx_handle_rx(struct wfx_dev *wdev, struct sk_buff *skb)
diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c
index 5b04ea5f4353..2e71f446d4d4 100644
--- a/drivers/staging/wfx/main.c
+++ b/drivers/staging/wfx/main.c
@@ -212,6 +212,7 @@ struct wfx_dev *wfx_init_common(struct device *dev,
 	wdev->pdata.gpio_wakeup = wfx_get_gpio(dev, gpio_wakeup, "wakeup");
 	wfx_fill_sl_key(dev, &wdev->pdata);
 
+	mutex_init(&wdev->rx_stats_lock);
 	init_completion(&wdev->firmware_ready);
 	wfx_init_hif_cmd(&wdev->hif_cmd);
 
@@ -220,6 +221,7 @@ struct wfx_dev *wfx_init_common(struct device *dev,
 
 void wfx_free_common(struct wfx_dev *wdev)
 {
+	mutex_destroy(&wdev->rx_stats_lock);
 	ieee80211_free_hw(wdev->hw);
 }
 
diff --git a/drivers/staging/wfx/wfx.h b/drivers/staging/wfx/wfx.h
index 7adb5bf67e90..49b776a07515 100644
--- a/drivers/staging/wfx/wfx.h
+++ b/drivers/staging/wfx/wfx.h
@@ -38,6 +38,9 @@ struct wfx_dev {
 	int			chip_frozen;
 
 	struct wfx_hif_cmd	hif_cmd;
+
+	struct hif_rx_stats	rx_stats;
+	struct mutex		rx_stats_lock;
 };
 
 struct wfx_vif {
@@ -46,4 +49,17 @@ struct wfx_vif {
 	int			id;
 };
 
+static inline struct wfx_vif *wdev_to_wvif(struct wfx_dev *wdev, int vif_id)
+{
+	if (vif_id >= ARRAY_SIZE(wdev->vif)) {
+		dev_dbg(wdev->dev, "requesting non-existent vif: %d\n", vif_id);
+		return NULL;
+	}
+	if (!wdev->vif[vif_id]) {
+		dev_dbg(wdev->dev, "requesting non-allocated vif: %d\n", vif_id);
+		return NULL;
+	}
+	return (struct wfx_vif *) wdev->vif[vif_id]->drv_priv;
+}
+
 #endif /* WFX_H */
-- 
2.20.1

^ permalink raw reply related

* [PATCH v2 12/20] staging: wfx: add HIF commands helpers
From: Jerome Pouiller @ 2019-09-19 13:52 UTC (permalink / raw)
  To: devel@driverdev.osuosl.org, linux-wireless@vger.kernel.org
  Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	Greg Kroah-Hartman, Kalle Valo, David S . Miller, David Le Goff,
	Jerome Pouiller
In-Reply-To: <20190919135220.30663-1-Jerome.Pouiller@silabs.com>

From: Jérôme Pouiller <jerome.pouiller@silabs.com>

Provide an abstraction for HIF commands.

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
 drivers/staging/wfx/hif_tx.c     | 375 +++++++++++++++++++++++++++++++
 drivers/staging/wfx/hif_tx.h     |  33 +++
 drivers/staging/wfx/hif_tx_mib.h | 281 +++++++++++++++++++++++
 3 files changed, 689 insertions(+)
 create mode 100644 drivers/staging/wfx/hif_tx_mib.h

diff --git a/drivers/staging/wfx/hif_tx.c b/drivers/staging/wfx/hif_tx.c
index f81a19089db4..781a6e28dbad 100644
--- a/drivers/staging/wfx/hif_tx.c
+++ b/drivers/staging/wfx/hif_tx.c
@@ -12,6 +12,7 @@
 #include "hif_tx.h"
 #include "wfx.h"
 #include "bh.h"
+#include "hwio.h"
 #include "debug.h"
 
 void wfx_init_hif_cmd(struct wfx_hif_cmd *hif_cmd)
@@ -21,6 +22,29 @@ void wfx_init_hif_cmd(struct wfx_hif_cmd *hif_cmd)
 	mutex_init(&hif_cmd->lock);
 }
 
+static void wfx_fill_header(struct hif_msg *hif, int if_id, unsigned int cmd, size_t size)
+{
+	if (if_id == -1)
+		if_id = 2;
+
+	WARN(cmd > 0x3f, "invalid WSM command %#.2x", cmd);
+	WARN(size > 0xFFF, "requested buffer is too large: %zu bytes", size);
+	WARN(if_id > 0x3, "invalid interface ID %d", if_id);
+
+	hif->len = cpu_to_le16(size + 4);
+	hif->id = cmd;
+	hif->interface = if_id;
+}
+
+static void *wfx_alloc_hif(size_t body_len, struct hif_msg **hif)
+{
+	*hif = kzalloc(sizeof(struct hif_msg) + body_len, GFP_KERNEL);
+	if (*hif)
+		return (*hif)->body;
+	else
+		return NULL;
+}
+
 int wfx_cmd_send(struct wfx_dev *wdev, struct hif_msg *request, void *reply, size_t reply_len, bool async)
 {
 	const char *mib_name = "";
@@ -85,3 +109,354 @@ int wfx_cmd_send(struct wfx_dev *wdev, struct hif_msg *request, void *reply, siz
 
 	return ret;
 }
+
+// This function is special. After HIF_REQ_ID_SHUT_DOWN, chip won't reply to any
+// request anymore. We need to slightly hack struct wfx_hif_cmd for that job. Be
+// carefull to only call this funcion during device unregister.
+int hif_shutdown(struct wfx_dev *wdev)
+{
+	int ret;
+	struct hif_msg *hif;
+
+	wfx_alloc_hif(0, &hif);
+	wfx_fill_header(hif, -1, HIF_REQ_ID_SHUT_DOWN, 0);
+	ret = wfx_cmd_send(wdev, hif, NULL, 0, true);
+	// After this command, chip won't reply. Be sure to give enough time to
+	// bh to send buffer:
+	msleep(100);
+	wdev->hif_cmd.buf_send = NULL;
+	if (wdev->pdata.gpio_wakeup)
+		gpiod_set_value(wdev->pdata.gpio_wakeup, 0);
+	else
+		control_reg_write(wdev, 0);
+	mutex_unlock(&wdev->hif_cmd.lock);
+	kfree(hif);
+	return ret;
+}
+
+int hif_configuration(struct wfx_dev *wdev, const u8 *conf, size_t len)
+{
+	int ret;
+	size_t buf_len = sizeof(struct hif_req_configuration) + len;
+	struct hif_msg *hif;
+	struct hif_req_configuration *body = wfx_alloc_hif(buf_len, &hif);
+
+	body->length = cpu_to_le16(len);
+	memcpy(body->pds_data, conf, len);
+	wfx_fill_header(hif, -1, HIF_REQ_ID_CONFIGURATION, buf_len);
+	ret = wfx_cmd_send(wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_reset(struct wfx_vif *wvif, bool reset_stat)
+{
+	int ret;
+	struct hif_msg *hif;
+	struct hif_req_reset *body = wfx_alloc_hif(sizeof(*body), &hif);
+
+	body->reset_flags.reset_stat = reset_stat;
+	wfx_fill_header(hif, wvif->id, HIF_REQ_ID_RESET, sizeof(*body));
+	ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_read_mib(struct wfx_dev *wdev, int vif_id, u16 mib_id, void *val, size_t val_len)
+{
+	int ret;
+	struct hif_msg *hif;
+	int buf_len = sizeof(struct hif_cnf_read_mib) + val_len;
+	struct hif_req_read_mib *body = wfx_alloc_hif(sizeof(*body), &hif);
+	struct hif_cnf_read_mib *reply = kmalloc(buf_len, GFP_KERNEL);
+
+	body->mib_id = cpu_to_le16(mib_id);
+	wfx_fill_header(hif, vif_id, HIF_REQ_ID_READ_MIB, sizeof(*body));
+	ret = wfx_cmd_send(wdev, hif, reply, buf_len, false);
+
+	if (!ret && mib_id != reply->mib_id) {
+		dev_warn(wdev->dev, "%s: confirmation mismatch request\n", __func__);
+		ret = -EIO;
+	}
+	if (ret == -ENOMEM)
+		dev_err(wdev->dev, "buffer is too small to receive %s (%zu < %d)\n",
+			get_mib_name(mib_id), val_len, reply->length);
+	if (!ret)
+		memcpy(val, &reply->mib_data, reply->length);
+	else
+		memset(val, 0xFF, val_len);
+	kfree(hif);
+	kfree(reply);
+	return ret;
+}
+
+int hif_write_mib(struct wfx_dev *wdev, int vif_id, u16 mib_id, void *val, size_t val_len)
+{
+	int ret;
+	struct hif_msg *hif;
+	int buf_len = sizeof(struct hif_req_write_mib) + val_len;
+	struct hif_req_write_mib *body = wfx_alloc_hif(buf_len, &hif);
+
+	body->mib_id = cpu_to_le16(mib_id);
+	body->length = cpu_to_le16(val_len);
+	memcpy(&body->mib_data, val, val_len);
+	wfx_fill_header(hif, vif_id, HIF_REQ_ID_WRITE_MIB, buf_len);
+	ret = wfx_cmd_send(wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_scan(struct wfx_vif *wvif, const struct wfx_scan_params *arg)
+{
+	int ret, i;
+	struct hif_msg *hif;
+	struct hif_ssid_def *ssids;
+	size_t buf_len = sizeof(struct hif_req_start_scan) +
+		arg->scan_req.num_of_channels * sizeof(u8) +
+		arg->scan_req.num_of_ssi_ds * sizeof(struct hif_ssid_def);
+	struct hif_req_start_scan *body = wfx_alloc_hif(buf_len, &hif);
+	u8 *ptr = (u8 *) body + sizeof(*body);
+
+	WARN(arg->scan_req.num_of_channels > HIF_API_MAX_NB_CHANNELS, "invalid params");
+	WARN(arg->scan_req.num_of_ssi_ds > 2, "invalid params");
+	WARN(arg->scan_req.band > 1, "invalid params");
+
+	// FIXME: This API is unnecessary complex, fixing NumOfChannels and
+	// adding a member SsidDef at end of struct hif_req_start_scan would
+	// simplify that a lot.
+	memcpy(body, &arg->scan_req, sizeof(*body));
+	cpu_to_le32s(&body->min_channel_time);
+	cpu_to_le32s(&body->max_channel_time);
+	cpu_to_le32s(&body->tx_power_level);
+	memcpy(ptr, arg->ssids, arg->scan_req.num_of_ssi_ds * sizeof(struct hif_ssid_def));
+	ssids = (struct hif_ssid_def *) ptr;
+	for (i = 0; i < body->num_of_ssi_ds; ++i)
+		cpu_to_le32s(&ssids[i].ssid_length);
+	ptr += arg->scan_req.num_of_ssi_ds * sizeof(struct hif_ssid_def);
+	memcpy(ptr, arg->ch, arg->scan_req.num_of_channels * sizeof(u8));
+	ptr += arg->scan_req.num_of_channels * sizeof(u8);
+	WARN(buf_len != ptr - (u8 *) body, "allocation size mismatch");
+	wfx_fill_header(hif, wvif->id, HIF_REQ_ID_START_SCAN, buf_len);
+	ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_stop_scan(struct wfx_vif *wvif)
+{
+	int ret;
+	struct hif_msg *hif;
+	// body associated to HIF_REQ_ID_STOP_SCAN is empty
+	wfx_alloc_hif(0, &hif);
+
+	wfx_fill_header(hif, wvif->id, HIF_REQ_ID_STOP_SCAN, 0);
+	ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_join(struct wfx_vif *wvif, const struct hif_req_join *arg)
+{
+	int ret;
+	struct hif_msg *hif;
+	struct hif_req_join *body = wfx_alloc_hif(sizeof(*body), &hif);
+
+	memcpy(body, arg, sizeof(struct hif_req_join));
+	cpu_to_le16s(&body->channel_number);
+	cpu_to_le16s(&body->atim_window);
+	cpu_to_le32s(&body->ssid_length);
+	cpu_to_le32s(&body->beacon_interval);
+	cpu_to_le32s(&body->basic_rate_set);
+	wfx_fill_header(hif, wvif->id, HIF_REQ_ID_JOIN, sizeof(*body));
+	ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_set_bss_params(struct wfx_vif *wvif, const struct hif_req_set_bss_params *arg)
+{
+	int ret;
+	struct hif_msg *hif;
+	struct hif_req_set_bss_params *body = wfx_alloc_hif(sizeof(*body), &hif);
+
+	memcpy(body, arg, sizeof(*body));
+	cpu_to_le16s(&body->aid);
+	cpu_to_le32s(&body->operational_rate_set);
+	wfx_fill_header(hif, wvif->id, HIF_REQ_ID_SET_BSS_PARAMS, sizeof(*body));
+	ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_add_key(struct wfx_dev *wdev, const struct hif_req_add_key *arg)
+{
+	int ret;
+	struct hif_msg *hif;
+	// FIXME: only send necessary bits
+	struct hif_req_add_key *body = wfx_alloc_hif(sizeof(*body), &hif);
+
+	// FIXME: swap bytes as necessary in body
+	memcpy(body, arg, sizeof(*body));
+	if (wfx_api_older_than(wdev, 1, 5))
+		// Legacy firmwares expect that add_key to be sent on right
+		// interface.
+		wfx_fill_header(hif, arg->int_id, HIF_REQ_ID_ADD_KEY, sizeof(*body));
+	else
+		wfx_fill_header(hif, -1, HIF_REQ_ID_ADD_KEY, sizeof(*body));
+	ret = wfx_cmd_send(wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_remove_key(struct wfx_dev *wdev, int idx)
+{
+	int ret;
+	struct hif_msg *hif;
+	struct hif_req_remove_key *body = wfx_alloc_hif(sizeof(*body), &hif);
+
+	body->entry_index = idx;
+	wfx_fill_header(hif, -1, HIF_REQ_ID_REMOVE_KEY, sizeof(*body));
+	ret = wfx_cmd_send(wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_set_edca_queue_params(struct wfx_vif *wvif, const struct hif_req_edca_queue_params *arg)
+{
+	int ret;
+	struct hif_msg *hif;
+	struct hif_req_edca_queue_params *body = wfx_alloc_hif(sizeof(*body), &hif);
+
+	// NOTE: queues numerotation are not the same between WFx and Linux
+	memcpy(body, arg, sizeof(*body));
+	cpu_to_le16s(&body->cw_min);
+	cpu_to_le16s(&body->cw_max);
+	cpu_to_le16s(&body->tx_op_limit);
+	wfx_fill_header(hif, wvif->id, HIF_REQ_ID_EDCA_QUEUE_PARAMS, sizeof(*body));
+	ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_set_pm(struct wfx_vif *wvif, const struct hif_req_set_pm_mode *arg)
+{
+	int ret;
+	struct hif_msg *hif;
+	struct hif_req_set_pm_mode *body = wfx_alloc_hif(sizeof(*body), &hif);
+
+	memcpy(body, arg, sizeof(*body));
+	wfx_fill_header(hif, wvif->id, HIF_REQ_ID_SET_PM_MODE, sizeof(*body));
+	ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_start(struct wfx_vif *wvif, const struct hif_req_start *arg)
+{
+	int ret;
+	struct hif_msg *hif;
+	struct hif_req_start *body = wfx_alloc_hif(sizeof(*body), &hif);
+
+	memcpy(body, arg, sizeof(*body));
+	cpu_to_le16s(&body->channel_number);
+	cpu_to_le32s(&body->beacon_interval);
+	cpu_to_le32s(&body->basic_rate_set);
+	wfx_fill_header(hif, wvif->id, HIF_REQ_ID_START, sizeof(*body));
+	ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_beacon_transmit(struct wfx_vif *wvif, bool enable_beaconing)
+{
+	int ret;
+	struct hif_msg *hif;
+	struct hif_req_beacon_transmit *body = wfx_alloc_hif(sizeof(*body), &hif);
+
+	body->enable_beaconing = enable_beaconing ? 1 : 0;
+	wfx_fill_header(hif, wvif->id, HIF_REQ_ID_BEACON_TRANSMIT, sizeof(*body));
+	ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_map_link(struct wfx_vif *wvif, u8 *mac_addr, int flags, int sta_id)
+{
+	int ret;
+	struct hif_msg *hif;
+	struct hif_req_map_link *body = wfx_alloc_hif(sizeof(*body), &hif);
+
+	if (mac_addr)
+		ether_addr_copy(body->mac_addr, mac_addr);
+	body->map_link_flags = *(struct hif_map_link_flags *) &flags;
+	body->peer_sta_id = sta_id;
+	wfx_fill_header(hif, wvif->id, HIF_REQ_ID_MAP_LINK, sizeof(*body));
+	ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_update_ie(struct wfx_vif *wvif, const struct hif_ie_flags *target_frame,
+		  const u8 *ies, size_t ies_len)
+{
+	int ret;
+	struct hif_msg *hif;
+	int buf_len = sizeof(struct hif_req_update_ie) + ies_len;
+	struct hif_req_update_ie *body = wfx_alloc_hif(buf_len, &hif);
+
+	memcpy(&body->ie_flags, target_frame, sizeof(struct hif_ie_flags));
+	body->num_i_es = cpu_to_le16(1);
+	memcpy(body->ie, ies, ies_len);
+	wfx_fill_header(hif, wvif->id, HIF_REQ_ID_UPDATE_IE, buf_len);
+	ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_sl_send_pub_keys(struct wfx_dev *wdev, const uint8_t *pubkey, const uint8_t *pubkey_hmac)
+{
+	int ret;
+	struct hif_msg *hif;
+	struct hif_req_sl_exchange_pub_keys *body = wfx_alloc_hif(sizeof(*body), &hif);
+
+	body->algorithm = HIF_SL_CURVE25519;
+	memcpy(body->host_pub_key, pubkey, sizeof(body->host_pub_key));
+	memcpy(body->host_pub_key_mac, pubkey_hmac, sizeof(body->host_pub_key_mac));
+	wfx_fill_header(hif, -1, HIF_REQ_ID_SL_EXCHANGE_PUB_KEYS, sizeof(*body));
+	ret = wfx_cmd_send(wdev, hif, NULL, 0, false);
+	kfree(hif);
+	// Compatibility with legacy secure link
+	if (ret == SL_PUB_KEY_EXCHANGE_STATUS_SUCCESS)
+		ret = 0;
+	return ret;
+}
+
+int hif_sl_config(struct wfx_dev *wdev, const unsigned long *bitmap)
+{
+	int ret;
+	struct hif_msg *hif;
+	struct hif_req_sl_configure *body = wfx_alloc_hif(sizeof(*body), &hif);
+
+	memcpy(body->encr_bmp, bitmap, sizeof(body->encr_bmp));
+	wfx_fill_header(hif, -1, HIF_REQ_ID_SL_CONFIGURE, sizeof(*body));
+	ret = wfx_cmd_send(wdev, hif, NULL, 0, false);
+	kfree(hif);
+	return ret;
+}
+
+int hif_sl_set_mac_key(struct wfx_dev *wdev, const uint8_t *slk_key, int destination)
+{
+	int ret;
+	struct hif_msg *hif;
+	struct hif_req_set_sl_mac_key *body = wfx_alloc_hif(sizeof(*body), &hif);
+
+	memcpy(body->key_value, slk_key, sizeof(body->key_value));
+	body->otp_or_ram = destination;
+	wfx_fill_header(hif, -1, HIF_REQ_ID_SET_SL_MAC_KEY, sizeof(*body));
+	ret = wfx_cmd_send(wdev, hif, NULL, 0, false);
+	kfree(hif);
+	// Compatibility with legacy secure link
+	if (ret == SL_MAC_KEY_STATUS_SUCCESS)
+		ret = 0;
+	return ret;
+}
diff --git a/drivers/staging/wfx/hif_tx.h b/drivers/staging/wfx/hif_tx.h
index ccf2b7e5e851..31f2a02c8466 100644
--- a/drivers/staging/wfx/hif_tx.h
+++ b/drivers/staging/wfx/hif_tx.h
@@ -15,6 +15,12 @@
 struct wfx_dev;
 struct wfx_vif;
 
+struct wfx_scan_params {
+	struct hif_req_start_scan scan_req;
+	struct hif_ssid_def *ssids;
+	uint8_t *ch;
+};
+
 struct wfx_hif_cmd {
 	struct mutex      lock;
 	struct completion ready;
@@ -30,4 +36,31 @@ void wfx_init_hif_cmd(struct wfx_hif_cmd *wfx_hif_cmd);
 int wfx_cmd_send(struct wfx_dev *wdev, struct hif_msg *request,
 		 void *reply, size_t reply_len, bool async);
 
+int hif_shutdown(struct wfx_dev *wdev);
+int hif_configuration(struct wfx_dev *wdev, const u8 *conf, size_t len);
+int hif_reset(struct wfx_vif *wvif, bool reset_stat);
+int hif_read_mib(struct wfx_dev *wdev, int vif_id, u16 mib_id,
+		 void *buf, size_t buf_size);
+int hif_write_mib(struct wfx_dev *wdev, int vif_id, u16 mib_id,
+		  void *buf, size_t buf_size);
+int hif_scan(struct wfx_vif *wvif, const struct wfx_scan_params *arg);
+int hif_stop_scan(struct wfx_vif *wvif);
+int hif_join(struct wfx_vif *wvif, const struct hif_req_join *arg);
+int hif_set_pm(struct wfx_vif *wvif, const struct hif_req_set_pm_mode *arg);
+int hif_set_bss_params(struct wfx_vif *wvif,
+		       const struct hif_req_set_bss_params *arg);
+int hif_add_key(struct wfx_dev *wdev, const struct hif_req_add_key *arg);
+int hif_remove_key(struct wfx_dev *wdev, int idx);
+int hif_set_edca_queue_params(struct wfx_vif *wvif,
+			      const struct hif_req_edca_queue_params *arg);
+int hif_start(struct wfx_vif *wvif, const struct hif_req_start *arg);
+int hif_beacon_transmit(struct wfx_vif *wvif, bool enable);
+int hif_map_link(struct wfx_vif *wvif, u8 *mac_addr, int flags, int sta_id);
+int hif_update_ie(struct wfx_vif *wvif, const struct hif_ie_flags *target_frame,
+		  const u8 *ies, size_t ies_len);
+int hif_sl_set_mac_key(struct wfx_dev *wdev, const uint8_t *slk_key, int destination);
+int hif_sl_config(struct wfx_dev *wdev, const unsigned long *bitmap);
+int hif_sl_send_pub_keys(struct wfx_dev *wdev,
+			 const uint8_t *pubkey, const uint8_t *pubkey_hmac);
+
 #endif
diff --git a/drivers/staging/wfx/hif_tx_mib.h b/drivers/staging/wfx/hif_tx_mib.h
new file mode 100644
index 000000000000..f6624a403016
--- /dev/null
+++ b/drivers/staging/wfx/hif_tx_mib.h
@@ -0,0 +1,281 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Implementation of host-to-chip MIBs of WFxxx Split Mac (WSM) API.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ * Copyright (C) 2010, ST-Ericsson SA
+ */
+#ifndef WFX_HIF_TX_MIB_H
+#define WFX_HIF_TX_MIB_H
+
+#include <linux/etherdevice.h>
+
+#include "wfx.h"
+#include "hif_tx.h"
+#include "hif_api_mib.h"
+
+static inline int hif_set_output_power(struct wfx_vif *wvif, int power_level)
+{
+	__le32 val = cpu_to_le32(power_level);
+
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_CURRENT_TX_POWER_LEVEL,
+			     &val, sizeof(val));
+}
+
+static inline int hif_set_beacon_wakeup_period(struct wfx_vif *wvif,
+					       unsigned int dtim_interval,
+					       unsigned int listen_interval)
+{
+	struct hif_mib_beacon_wake_up_period val = {
+		.wakeup_period_min = dtim_interval,
+		.receive_dtim = 0,
+		.wakeup_period_max = cpu_to_le16(listen_interval),
+	};
+
+	if (dtim_interval > 0xFF || listen_interval > 0xFFFF)
+		return -EINVAL;
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_BEACON_WAKEUP_PERIOD,
+			     &val, sizeof(val));
+}
+
+static inline int hif_set_rcpi_rssi_threshold(struct wfx_vif *wvif,
+					      struct hif_mib_rcpi_rssi_threshold *arg)
+{
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_RCPI_RSSI_THRESHOLD, arg, sizeof(*arg));
+}
+
+static inline int hif_get_counters_table(struct wfx_dev *wdev,
+					 struct hif_mib_extended_count_table *arg)
+{
+	if (wfx_api_older_than(wdev, 1, 3)) {
+		// extended_count_table is wider than count_table
+		memset(arg, 0xFF, sizeof(*arg));
+		return hif_read_mib(wdev, 0, HIF_MIB_ID_COUNTERS_TABLE,
+				    arg, sizeof(struct hif_mib_count_table));
+	} else {
+		return hif_read_mib(wdev, 0, HIF_MIB_ID_EXTENDED_COUNTERS_TABLE,
+				    arg, sizeof(struct hif_mib_extended_count_table));
+	}
+}
+
+static inline int hif_set_macaddr(struct wfx_vif *wvif, u8 *mac)
+{
+	struct hif_mib_mac_address msg = { };
+
+	if (mac)
+		ether_addr_copy(msg.mac_addr, mac);
+	return hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_DOT11_MAC_ADDRESS,
+			     &msg, sizeof(msg));
+}
+
+static inline int hif_set_rx_filter(struct wfx_vif *wvif, bool filter_bssid,
+				    bool fwd_probe_req)
+{
+	struct hif_mib_rx_filter val = { };
+
+	if (filter_bssid)
+		val.bssid_filter = 1;
+	if (fwd_probe_req)
+		val.fwd_probe_req = 1;
+	return hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_RX_FILTER,
+			     &val, sizeof(val));
+}
+
+static inline int hif_set_beacon_filter_table(struct wfx_vif *wvif,
+					      struct hif_mib_bcn_filter_table *ft)
+{
+	size_t buf_len = struct_size(ft, ie_table, ft->num_of_info_elmts);
+
+	cpu_to_le32s(&ft->num_of_info_elmts);
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_BEACON_FILTER_TABLE, ft, buf_len);
+}
+
+static inline int hif_beacon_filter_control(struct wfx_vif *wvif,
+					    int enable, int beacon_count)
+{
+	struct hif_mib_bcn_filter_enable arg = {
+		.enable = cpu_to_le32(enable),
+		.bcn_count = cpu_to_le32(beacon_count),
+	};
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_BEACON_FILTER_ENABLE, &arg, sizeof(arg));
+}
+
+static inline int hif_set_operational_mode(struct wfx_dev *wdev,
+					   enum hif_op_power_mode mode)
+{
+	struct hif_mib_gl_operational_power_mode val = {
+		.power_mode = mode,
+		.wup_ind_activation = 1,
+	};
+
+	return hif_write_mib(wdev, -1, HIF_MIB_ID_GL_OPERATIONAL_POWER_MODE,
+			     &val, sizeof(val));
+}
+
+static inline int hif_set_template_frame(struct wfx_vif *wvif,
+					 struct hif_mib_template_frame *arg)
+{
+	return hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_TEMPLATE_FRAME,
+			     arg, sizeof(*arg));
+}
+
+static inline int hif_set_mfp(struct wfx_vif *wvif, bool capable, bool required)
+{
+	struct hif_mib_protected_mgmt_policy val = { };
+
+	WARN_ON(required && !capable);
+	if (capable) {
+		val.pmf_enable = 1;
+		val.host_enc_auth_frames = 1;
+	}
+	if (!required)
+		val.unpmf_allowed = 1;
+	cpu_to_le32s(&val);
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_PROTECTED_MGMT_POLICY,
+			     &val, sizeof(val));
+}
+
+static inline int hif_set_block_ack_policy(struct wfx_vif *wvif,
+					   u8 tx_tid_policy, u8 rx_tid_policy)
+{
+	struct hif_mib_block_ack_policy val = {
+		.block_ack_tx_tid_policy = tx_tid_policy,
+		.block_ack_rx_tid_policy = rx_tid_policy,
+	};
+
+	return hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_BLOCK_ACK_POLICY,
+			     &val, sizeof(val));
+}
+
+static inline int hif_set_association_mode(struct wfx_vif *wvif,
+					   struct hif_mib_set_association_mode *arg)
+{
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_SET_ASSOCIATION_MODE, arg, sizeof(*arg));
+}
+
+static inline int hif_set_tx_rate_retry_policy(struct wfx_vif *wvif,
+					       struct hif_mib_set_tx_rate_retry_policy *arg)
+{
+	size_t size = struct_size(arg, tx_rate_retry_policy, arg->num_tx_rate_policies);
+
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_SET_TX_RATE_RETRY_POLICY, arg, size);
+}
+
+static inline int hif_set_mac_addr_condition(struct wfx_vif *wvif,
+					     struct hif_mib_mac_addr_data_frame_condition *arg)
+{
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_MAC_ADDR_DATAFRAME_CONDITION,
+			     arg, sizeof(*arg));
+}
+
+static inline int hif_set_uc_mc_bc_condition(struct wfx_vif *wvif,
+					     struct hif_mib_uc_mc_bc_data_frame_condition *arg)
+{
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_UC_MC_BC_DATAFRAME_CONDITION,
+			     arg, sizeof(*arg));
+}
+
+static inline int hif_set_config_data_filter(struct wfx_vif *wvif,
+					     struct hif_mib_config_data_filter *arg)
+{
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_CONFIG_DATA_FILTER, arg, sizeof(*arg));
+}
+
+static inline int hif_set_data_filtering(struct wfx_vif *wvif,
+					 struct hif_mib_set_data_filtering *arg)
+{
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_SET_DATA_FILTERING, arg, sizeof(*arg));
+}
+
+static inline int hif_keep_alive_period(struct wfx_vif *wvif, int period)
+{
+	struct hif_mib_keep_alive_period arg = {
+		.keep_alive_period = cpu_to_le16(period),
+	};
+
+	return hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_KEEP_ALIVE_PERIOD,
+			     &arg, sizeof(arg));
+};
+
+static inline int hif_set_arp_ipv4_filter(struct wfx_vif *wvif,
+					  struct hif_mib_arp_ip_addr_table *fp)
+{
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_ARP_IP_ADDRESSES_TABLE,
+			     fp, sizeof(*fp));
+}
+
+static inline int hif_use_multi_tx_conf(struct wfx_dev *wdev,
+					bool enabled)
+{
+	__le32 arg = enabled ? cpu_to_le32(1) : 0;
+
+	return hif_write_mib(wdev, -1, HIF_MIB_ID_GL_SET_MULTI_MSG,
+			     &arg, sizeof(arg));
+}
+
+static inline int hif_set_uapsd_info(struct wfx_vif *wvif,
+				     struct hif_mib_set_uapsd_information *arg)
+{
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_SET_UAPSD_INFORMATION,
+			     arg, sizeof(*arg));
+}
+
+static inline int hif_erp_use_protection(struct wfx_vif *wvif, bool enable)
+{
+	__le32 arg = enable ? cpu_to_le32(1) : 0;
+
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_NON_ERP_PROTECTION, &arg, sizeof(arg));
+}
+
+static inline int hif_slot_time(struct wfx_vif *wvif, int val)
+{
+	__le32 arg = cpu_to_le32(val);
+
+	return hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_SLOT_TIME,
+			     &arg, sizeof(arg));
+}
+
+static inline int hif_dual_cts_protection(struct wfx_vif *wvif, bool val)
+{
+	struct hif_mib_set_ht_protection arg = {
+		.dual_cts_prot = val,
+	};
+
+	return hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_SET_HT_PROTECTION,
+			     &arg, sizeof(arg));
+}
+
+static inline int hif_wep_default_key_id(struct wfx_vif *wvif, int val)
+{
+	__le32 arg = cpu_to_le32(val);
+
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_DOT11_WEP_DEFAULT_KEY_ID,
+			     &arg, sizeof(arg));
+}
+
+static inline int hif_rts_threshold(struct wfx_vif *wvif, int val)
+{
+	__le32 arg = cpu_to_le32(val > 0 ? val : 0xFFFF);
+
+	return hif_write_mib(wvif->wdev, wvif->id,
+			     HIF_MIB_ID_DOT11_RTS_THRESHOLD, &arg, sizeof(arg));
+}
+
+#endif
-- 
2.20.1

^ permalink raw reply related

* [PATCH v2 17/20] staging: wfx: allow to receive 802.11 frames
From: Jerome Pouiller @ 2019-09-19 13:52 UTC (permalink / raw)
  To: devel@driverdev.osuosl.org, linux-wireless@vger.kernel.org
  Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	Greg Kroah-Hartman, Kalle Valo, David S . Miller, David Le Goff,
	Jerome Pouiller
In-Reply-To: <20190919135220.30663-1-Jerome.Pouiller@silabs.com>

From: Jérôme Pouiller <jerome.pouiller@silabs.com>

Again, this task is more complex than it should since driver try to
handle itself power saving of stations.

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
 drivers/staging/wfx/Makefile  |   1 +
 drivers/staging/wfx/data_rx.c | 182 ++++++++++++++++++++++++++++++++++
 drivers/staging/wfx/data_rx.h |  18 ++++
 drivers/staging/wfx/hif_rx.c  |  23 +++++
 4 files changed, 224 insertions(+)
 create mode 100644 drivers/staging/wfx/data_rx.c
 create mode 100644 drivers/staging/wfx/data_rx.h

diff --git a/drivers/staging/wfx/Makefile b/drivers/staging/wfx/Makefile
index d5ac9fafd1f1..d9e21515d08e 100644
--- a/drivers/staging/wfx/Makefile
+++ b/drivers/staging/wfx/Makefile
@@ -11,6 +11,7 @@ wfx-y := \
 	hif_rx.o \
 	queue.o \
 	data_tx.o \
+	data_rx.o \
 	sta.o \
 	main.o \
 	sta.o \
diff --git a/drivers/staging/wfx/data_rx.c b/drivers/staging/wfx/data_rx.c
new file mode 100644
index 000000000000..3b3117b2edac
--- /dev/null
+++ b/drivers/staging/wfx/data_rx.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Datapath implementation.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#include <linux/etherdevice.h>
+#include <net/mac80211.h>
+
+#include "data_rx.h"
+#include "wfx.h"
+#include "bh.h"
+#include "sta.h"
+
+static int wfx_handle_pspoll(struct wfx_vif *wvif, struct sk_buff *skb)
+{
+	struct ieee80211_sta *sta;
+	struct ieee80211_pspoll *pspoll = (struct ieee80211_pspoll *)skb->data;
+	int link_id = 0;
+	u32 pspoll_mask = 0;
+	int i;
+
+	if (!ether_addr_equal(wvif->vif->addr, pspoll->bssid))
+		return 1;
+
+	rcu_read_lock();
+	sta = ieee80211_find_sta(wvif->vif, pspoll->ta);
+	if (sta)
+		link_id = ((struct wfx_sta_priv *) &sta->drv_priv)->link_id;
+	rcu_read_unlock();
+	if (link_id)
+		pspoll_mask = BIT(link_id);
+	else
+		return 1;
+
+	wvif->pspoll_mask |= pspoll_mask;
+	/* Do not report pspols if data for given link id is queued already. */
+	for (i = 0; i < IEEE80211_NUM_ACS; ++i) {
+		if (wfx_tx_queue_get_num_queued(&wvif->wdev->tx_queue[i],
+						pspoll_mask)) {
+			wfx_bh_request_tx(wvif->wdev);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int wfx_drop_encrypt_data(struct wfx_dev *wdev, struct hif_ind_rx *arg, struct sk_buff *skb)
+{
+	struct ieee80211_hdr *frame = (struct ieee80211_hdr *) skb->data;
+	size_t hdrlen = ieee80211_hdrlen(frame->frame_control);
+	size_t iv_len, icv_len;
+
+	/* Oops... There is no fast way to ask mac80211 about
+	 * IV/ICV lengths. Even defineas are not exposed.
+	 */
+	switch (arg->rx_flags.encryp) {
+	case HIF_RI_FLAGS_WEP_ENCRYPTED:
+		iv_len = 4 /* WEP_IV_LEN */;
+		icv_len = 4 /* WEP_ICV_LEN */;
+		break;
+	case HIF_RI_FLAGS_TKIP_ENCRYPTED:
+		iv_len = 8 /* TKIP_IV_LEN */;
+		icv_len = 4 /* TKIP_ICV_LEN */
+			+ 8 /*MICHAEL_MIC_LEN*/;
+		break;
+	case HIF_RI_FLAGS_AES_ENCRYPTED:
+		iv_len = 8 /* CCMP_HDR_LEN */;
+		icv_len = 8 /* CCMP_MIC_LEN */;
+		break;
+	case HIF_RI_FLAGS_WAPI_ENCRYPTED:
+		iv_len = 18 /* WAPI_HDR_LEN */;
+		icv_len = 16 /* WAPI_MIC_LEN */;
+		break;
+	default:
+		dev_err(wdev->dev, "unknown encryption type %d\n",
+			 arg->rx_flags.encryp);
+		return -EIO;
+	}
+
+	/* Firmware strips ICV in case of MIC failure. */
+	if (arg->status == HIF_STATUS_MICFAILURE)
+		icv_len = 0;
+
+	if (skb->len < hdrlen + iv_len + icv_len) {
+		dev_warn(wdev->dev, "malformed SDU received\n");
+		return -EIO;
+	}
+
+	/* Remove IV, ICV and MIC */
+	skb_trim(skb, skb->len - icv_len);
+	memmove(skb->data + iv_len, skb->data, hdrlen);
+	skb_pull(skb, iv_len);
+	return 0;
+
+}
+
+void wfx_rx_cb(struct wfx_vif *wvif, struct hif_ind_rx *arg, struct sk_buff *skb)
+{
+	int link_id = arg->rx_flags.peer_sta_id;
+	struct ieee80211_rx_status *hdr = IEEE80211_SKB_RXCB(skb);
+	struct ieee80211_hdr *frame = (struct ieee80211_hdr *) skb->data;
+	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *) skb->data;
+	struct wfx_link_entry *entry = NULL;
+	bool early_data = false;
+
+	memset(hdr, 0, sizeof(*hdr));
+
+	// FIXME: Why do we drop these frames?
+	if (!arg->rcpi_rssi &&
+	    (ieee80211_is_probe_resp(frame->frame_control) ||
+	     ieee80211_is_beacon(frame->frame_control)))
+		goto drop;
+
+	if (link_id && link_id <= WFX_MAX_STA_IN_AP_MODE) {
+		entry = &wvif->link_id_db[link_id - 1];
+		entry->timestamp = jiffies;
+		if (entry->status == WFX_LINK_SOFT && ieee80211_is_data(frame->frame_control))
+			early_data = true;
+	}
+
+	if (arg->status == HIF_STATUS_MICFAILURE)
+		hdr->flag |= RX_FLAG_MMIC_ERROR;
+	else if (arg->status)
+		goto drop;
+
+	if (skb->len < sizeof(struct ieee80211_pspoll)) {
+		dev_warn(wvif->wdev->dev, "malformed SDU received\n");
+		goto drop;
+	}
+
+	if (ieee80211_is_pspoll(frame->frame_control))
+		if (wfx_handle_pspoll(wvif, skb))
+			goto drop;
+
+	hdr->band = NL80211_BAND_2GHZ;
+	hdr->freq = ieee80211_channel_to_frequency(arg->channel_number, hdr->band);
+
+	if (arg->rxed_rate >= 14) {
+		hdr->encoding = RX_ENC_HT;
+		hdr->rate_idx = arg->rxed_rate - 14;
+	} else if (arg->rxed_rate >= 4) {
+		hdr->rate_idx = arg->rxed_rate - 2;
+	} else {
+		hdr->rate_idx = arg->rxed_rate;
+	}
+
+	hdr->signal = arg->rcpi_rssi / 2 - 110;
+	hdr->antenna = 0;
+
+	if (arg->rx_flags.encryp) {
+		if (wfx_drop_encrypt_data(wvif->wdev, arg, skb))
+			goto drop;
+		hdr->flag |= RX_FLAG_DECRYPTED | RX_FLAG_IV_STRIPPED;
+		if (arg->rx_flags.encryp == HIF_RI_FLAGS_TKIP_ENCRYPTED)
+			hdr->flag |= RX_FLAG_MMIC_STRIPPED;
+	}
+
+	/* Filter block ACK negotiation: fully controlled by firmware */
+	if (ieee80211_is_action(frame->frame_control)
+	    && arg->rx_flags.match_uc_addr
+	    && mgmt->u.action.category == WLAN_CATEGORY_BACK)
+		goto drop;
+
+	if (early_data) {
+		spin_lock_bh(&wvif->ps_state_lock);
+		/* Double-check status with lock held */
+		if (entry->status == WFX_LINK_SOFT)
+			skb_queue_tail(&entry->rx_queue, skb);
+		else
+			ieee80211_rx_irqsafe(wvif->wdev->hw, skb);
+		spin_unlock_bh(&wvif->ps_state_lock);
+	} else {
+		ieee80211_rx_irqsafe(wvif->wdev->hw, skb);
+	}
+
+	return;
+
+drop:
+	dev_kfree_skb(skb);
+}
diff --git a/drivers/staging/wfx/data_rx.h b/drivers/staging/wfx/data_rx.h
new file mode 100644
index 000000000000..b44d15268f83
--- /dev/null
+++ b/drivers/staging/wfx/data_rx.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Datapath implementation.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#ifndef WFX_DATA_RX_H
+#define WFX_DATA_RX_H
+
+#include "hif_api_cmd.h"
+
+struct wfx_vif;
+struct sk_buff;
+
+void wfx_rx_cb(struct wfx_vif *wvif, struct hif_ind_rx *arg, struct sk_buff *skb);
+
+#endif /* WFX_DATA_RX_H */
diff --git a/drivers/staging/wfx/hif_rx.c b/drivers/staging/wfx/hif_rx.c
index 97c4c2f082fb..c07984b0535d 100644
--- a/drivers/staging/wfx/hif_rx.c
+++ b/drivers/staging/wfx/hif_rx.c
@@ -11,6 +11,7 @@
 
 #include "hif_rx.h"
 #include "wfx.h"
+#include "data_rx.h"
 #include "secure_link.h"
 #include "hif_api_cmd.h"
 
@@ -127,6 +128,21 @@ static int hif_keys_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *
 	return 0;
 }
 
+static int hif_receive_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf, struct sk_buff *skb)
+{
+	struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
+	struct hif_ind_rx *body = buf;
+
+	if (!wvif) {
+		dev_warn(wdev->dev, "ignore rx data for non existant vif %d\n", hif->interface);
+		return 0;
+	}
+	skb_pull(skb, sizeof(struct hif_msg) + sizeof(struct hif_ind_rx));
+	wfx_rx_cb(wvif, body, skb);
+
+	return 0;
+}
+
 static int hif_join_complete_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
 {
 	struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
@@ -218,6 +234,8 @@ static const struct {
 	{ HIF_IND_ID_GENERIC,              hif_generic_indication },
 	{ HIF_IND_ID_ERROR,                hif_error_indication },
 	{ HIF_IND_ID_EXCEPTION,            hif_exception_indication },
+	// FIXME: allocate skb_p from hif_receive_indication and make it generic
+	//{ HIF_IND_ID_RX,                 hif_receive_indication },
 };
 
 void wfx_handle_rx(struct wfx_dev *wdev, struct sk_buff *skb)
@@ -226,6 +244,11 @@ void wfx_handle_rx(struct wfx_dev *wdev, struct sk_buff *skb)
 	struct hif_msg *hif = (struct hif_msg *) skb->data;
 	int hif_id = hif->id;
 
+	if (hif_id == HIF_IND_ID_RX) {
+		// hif_receive_indication take care of skb lifetime
+		hif_receive_indication(wdev, hif, hif->body, skb);
+		return;
+	}
 	// Note: mutex_is_lock cause an implicit memory barrier that protect
 	// buf_send
 	if (mutex_is_locked(&wdev->hif_cmd.lock)
-- 
2.20.1

^ permalink raw reply related

* [PATCH v2 14/20] staging: wfx: setup initial chip configuration
From: Jerome Pouiller @ 2019-09-19 13:52 UTC (permalink / raw)
  To: devel@driverdev.osuosl.org, linux-wireless@vger.kernel.org
  Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	Greg Kroah-Hartman, Kalle Valo, David S . Miller, David Le Goff,
	Jerome Pouiller
In-Reply-To: <20190919135220.30663-1-Jerome.Pouiller@silabs.com>

From: Jérôme Pouiller <jerome.pouiller@silabs.com>

A few tasks remain to be done in order to finish chip initial
configuration:
   - configure chip to use multi-tx confirmation (speed up data
     transfer)
   - configure chip to use wake-up feature (save power consumption
     during runtime)
   - set hardware configuration (clocks, RF, pinout, etc...) using a
     Platform Data Set (PDS) file

On release, driver completely shutdown the chip to save power
consumption.

Documentation about PDS and PDS data for sample boards are available
here[1]. One day, PDS data may find a place in device tree but,
currently, PDS is too much linked with firmware to allowing that.

This patch also add "send_pds" file in debugfs to be able to dynamically
change PDS (only for debug, of course).

[1]: https://github.com/SiliconLabs/wfx-firmware/tree/master/PDS

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
 drivers/staging/wfx/bus_sdio.c |  1 +
 drivers/staging/wfx/bus_spi.c  |  1 +
 drivers/staging/wfx/debug.c    | 29 +++++++++++
 drivers/staging/wfx/hif_rx.c   | 11 ++++
 drivers/staging/wfx/main.c     | 94 ++++++++++++++++++++++++++++++++++
 drivers/staging/wfx/main.h     |  2 +
 6 files changed, 138 insertions(+)

diff --git a/drivers/staging/wfx/bus_sdio.c b/drivers/staging/wfx/bus_sdio.c
index c0c063c3cfc9..05f02c278782 100644
--- a/drivers/staging/wfx/bus_sdio.c
+++ b/drivers/staging/wfx/bus_sdio.c
@@ -19,6 +19,7 @@
 
 static const struct wfx_platform_data wfx_sdio_pdata = {
 	.file_fw = "wfm_wf200",
+	.file_pds = "wf200.pds",
 };
 
 struct wfx_sdio_priv {
diff --git a/drivers/staging/wfx/bus_spi.c b/drivers/staging/wfx/bus_spi.c
index b7cd82b4e5e7..f65f7d75e731 100644
--- a/drivers/staging/wfx/bus_spi.c
+++ b/drivers/staging/wfx/bus_spi.c
@@ -29,6 +29,7 @@ MODULE_PARM_DESC(gpio_reset, "gpio number for reset. -1 for none.");
 
 static const struct wfx_platform_data wfx_spi_pdata = {
 	.file_fw = "wfm_wf200",
+	.file_pds = "wf200.pds",
 	.use_rising_clk = true,
 };
 
diff --git a/drivers/staging/wfx/debug.c b/drivers/staging/wfx/debug.c
index f79693a4be7f..0619c7d1cf79 100644
--- a/drivers/staging/wfx/debug.c
+++ b/drivers/staging/wfx/debug.c
@@ -10,6 +10,7 @@
 
 #include "debug.h"
 #include "wfx.h"
+#include "main.h"
 
 #define CREATE_TRACE_POINTS
 #include "traces.h"
@@ -54,6 +55,33 @@ const char *get_reg_name(unsigned long id)
 	return get_symbol(id, wfx_reg_print_map);
 }
 
+static ssize_t wfx_send_pds_write(struct file *file, const char __user *user_buf,
+			     size_t count, loff_t *ppos)
+{
+	struct wfx_dev *wdev = file->private_data;
+	char *buf;
+	int ret;
+
+	if (*ppos != 0) {
+		dev_dbg(wdev->dev, "PDS data must be written in one transaction");
+		return -EBUSY;
+	}
+	buf = memdup_user(user_buf, count);
+	if (IS_ERR(buf))
+		return PTR_ERR(buf);
+	*ppos = *ppos + count;
+	ret = wfx_send_pds(wdev, buf, count);
+	kfree(buf);
+	if (ret < 0)
+		return ret;
+	return count;
+}
+
+static const struct file_operations wfx_send_pds_fops = {
+	.open = simple_open,
+	.write = wfx_send_pds_write,
+};
+
 static ssize_t wfx_burn_slk_key_write(struct file *file,
 				      const char __user *user_buf,
 				      size_t count, loff_t *ppos)
@@ -162,6 +190,7 @@ int wfx_debug_init(struct wfx_dev *wdev)
 	struct dentry *d;
 
 	d = debugfs_create_dir("wfx", wdev->hw->wiphy->debugfsdir);
+	debugfs_create_file("send_pds", 0200, d, wdev, &wfx_send_pds_fops);
 	debugfs_create_file("burn_slk_key", 0200, d, wdev, &wfx_burn_slk_key_fops);
 	debugfs_create_file("send_hif_msg", 0600, d, wdev, &wfx_send_hif_msg_fops);
 
diff --git a/drivers/staging/wfx/hif_rx.c b/drivers/staging/wfx/hif_rx.c
index dd5f1dea4e85..6b9683d69a3f 100644
--- a/drivers/staging/wfx/hif_rx.c
+++ b/drivers/staging/wfx/hif_rx.c
@@ -71,6 +71,16 @@ static int hif_startup_indication(struct wfx_dev *wdev, struct hif_msg *hif, voi
 	return 0;
 }
 
+static int hif_wakeup_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
+{
+	if (!wdev->pdata.gpio_wakeup
+	    || !gpiod_get_value(wdev->pdata.gpio_wakeup)) {
+		dev_warn(wdev->dev, "unexpected wake-up indication\n");
+		return -EIO;
+	}
+	return 0;
+}
+
 static int hif_keys_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
 {
 	struct hif_ind_sl_exchange_pub_keys *body = buf;
@@ -89,6 +99,7 @@ static const struct {
 	int (*handler)(struct wfx_dev *wdev, struct hif_msg *hif, void *buf);
 } hif_handlers[] = {
 	{ HIF_IND_ID_STARTUP,              hif_startup_indication },
+	{ HIF_IND_ID_WAKEUP,               hif_wakeup_indication },
 	{ HIF_IND_ID_SL_EXCHANGE_PUB_KEYS, hif_keys_indication },
 };
 
diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c
index 0cfd6b2ec8d1..5b04ea5f4353 100644
--- a/drivers/staging/wfx/main.c
+++ b/drivers/staging/wfx/main.c
@@ -18,6 +18,7 @@
 #include <linux/mmc/sdio_func.h>
 #include <linux/spi/spi.h>
 #include <linux/etherdevice.h>
+#include <linux/firmware.h>
 
 #include "main.h"
 #include "wfx.h"
@@ -28,9 +29,12 @@
 #include "sta.h"
 #include "debug.h"
 #include "secure_link.h"
+#include "hif_tx_mib.h"
 #include "hif_api_cmd.h"
 #include "wfx_version.h"
 
+#define WFX_PDS_MAX_SIZE 1500
+
 MODULE_DESCRIPTION("Silicon Labs 802.11 Wireless LAN driver for WFx");
 MODULE_AUTHOR("Jérôme Pouiller <jerome.pouiller@silabs.com>");
 MODULE_LICENSE("GPL");
@@ -112,6 +116,69 @@ static void wfx_fill_sl_key(struct device *dev, struct wfx_platform_data *pdata)
 	dev_err(dev, "secure link is not supported by this driver, ignoring provided key\n");
 }
 
+/* NOTE: wfx_send_pds() destroy buf */
+int wfx_send_pds(struct wfx_dev *wdev, unsigned char *buf, size_t len)
+{
+	int ret;
+	int start, brace_level, i;
+
+	start = 0;
+	brace_level = 0;
+	if (buf[0] != '{') {
+		dev_err(wdev->dev, "valid PDS start with '{'. Did you forget to compress it?\n");
+		return -EINVAL;
+	}
+	for (i = 1; i < len - 1; i++) {
+		if (buf[i] == '{')
+			brace_level++;
+		if (buf[i] == '}')
+			brace_level--;
+		if (buf[i] == '}' && !brace_level) {
+			i++;
+			if (i - start + 1 > WFX_PDS_MAX_SIZE)
+				return -EFBIG;
+			buf[start] = '{';
+			buf[i] = 0;
+			dev_dbg(wdev->dev, "send PDS '%s}'\n", buf + start);
+			buf[i] = '}';
+			ret = hif_configuration(wdev, buf + start, i - start + 1);
+			if (ret == HIF_STATUS_FAILURE) {
+				dev_err(wdev->dev, "PDS bytes %d to %d: invalid data (unsupported options?)\n", start, i);
+				return -EINVAL;
+			}
+			if (ret == -ETIMEDOUT) {
+				dev_err(wdev->dev, "PDS bytes %d to %d: chip didn't reply (corrupted file?)\n", start, i);
+				return ret;
+			}
+			if (ret) {
+				dev_err(wdev->dev, "PDS bytes %d to %d: chip returned an unknown error\n", start, i);
+				return -EIO;
+			}
+			buf[i] = ',';
+			start = i;
+		}
+	}
+	return 0;
+}
+
+static int wfx_send_pdata_pds(struct wfx_dev *wdev)
+{
+	int ret = 0;
+	const struct firmware *pds;
+	unsigned char *tmp_buf;
+
+	ret = request_firmware(&pds, wdev->pdata.file_pds, wdev->dev);
+	if (ret) {
+		dev_err(wdev->dev, "can't load PDS file %s\n", wdev->pdata.file_pds);
+		return ret;
+	}
+	tmp_buf = kmemdup(pds->data, pds->size, GFP_KERNEL);
+	ret = wfx_send_pds(wdev, tmp_buf, pds->size);
+	kfree(tmp_buf);
+	release_firmware(pds);
+	return ret;
+}
+
 struct wfx_dev *wfx_init_common(struct device *dev,
 				const struct wfx_platform_data *pdata,
 				const struct hwbus_ops *hwbus_ops,
@@ -141,6 +208,8 @@ struct wfx_dev *wfx_init_common(struct device *dev,
 	wdev->hwbus_ops = hwbus_ops;
 	wdev->hwbus_priv = hwbus_priv;
 	memcpy(&wdev->pdata, pdata, sizeof(*pdata));
+	of_property_read_string(dev->of_node, "config-file", &wdev->pdata.file_pds);
+	wdev->pdata.gpio_wakeup = wfx_get_gpio(dev, gpio_wakeup, "wakeup");
 	wfx_fill_sl_key(dev, &wdev->pdata);
 
 	init_completion(&wdev->firmware_ready);
@@ -159,6 +228,12 @@ int wfx_probe(struct wfx_dev *wdev)
 	int i;
 	int err;
 	const void *macaddr;
+	struct gpio_desc *gpio_saved;
+
+	// During first part of boot, gpio_wakeup cannot yet been used. So
+	// prevent bh() to touch it.
+	gpio_saved = wdev->pdata.gpio_wakeup;
+	wdev->pdata.gpio_wakeup = NULL;
 
 	wfx_bh_register(wdev);
 
@@ -202,6 +277,24 @@ int wfx_probe(struct wfx_dev *wdev)
 		goto err1;
 	}
 
+	dev_dbg(wdev->dev, "sending configuration file %s\n", wdev->pdata.file_pds);
+	err = wfx_send_pdata_pds(wdev);
+	if (err < 0)
+		goto err1;
+
+	wdev->pdata.gpio_wakeup = gpio_saved;
+	if (wdev->pdata.gpio_wakeup) {
+		dev_dbg(wdev->dev, "enable 'quiescent' power mode with gpio %d and PDS file %s\n",
+			desc_to_gpio(wdev->pdata.gpio_wakeup), wdev->pdata.file_pds);
+		gpiod_set_value(wdev->pdata.gpio_wakeup, 1);
+		control_reg_write(wdev, 0);
+		hif_set_operational_mode(wdev, HIF_OP_POWER_MODE_QUIESCENT);
+	} else {
+		hif_set_operational_mode(wdev, HIF_OP_POWER_MODE_DOZE);
+	}
+
+	hif_use_multi_tx_conf(wdev, true);
+
 	for (i = 0; i < ARRAY_SIZE(wdev->addresses); i++) {
 		eth_zero_addr(wdev->addresses[i].addr);
 		macaddr = of_get_mac_address(wdev->dev->of_node);
@@ -232,6 +325,7 @@ int wfx_probe(struct wfx_dev *wdev)
 
 void wfx_release(struct wfx_dev *wdev)
 {
+	hif_shutdown(wdev);
 	wfx_bh_unregister(wdev);
 	wfx_sl_deinit(wdev);
 }
diff --git a/drivers/staging/wfx/main.h b/drivers/staging/wfx/main.h
index 2c9c215455ce..f2b07ed1627c 100644
--- a/drivers/staging/wfx/main.h
+++ b/drivers/staging/wfx/main.h
@@ -21,6 +21,7 @@ struct wfx_dev;
 struct wfx_platform_data {
 	/* Keyset and ".sec" extention will appended to this string */
 	const char *file_fw;
+	const char *file_pds;
 	unsigned char slk_key[API_KEY_VALUE_SIZE];
 	struct gpio_desc *gpio_wakeup;
 	/*
@@ -42,5 +43,6 @@ void wfx_release(struct wfx_dev *wdev);
 struct gpio_desc *wfx_get_gpio(struct device *dev, int override,
 			       const char *label);
 bool wfx_api_older_than(struct wfx_dev *wdev, int major, int minor);
+int wfx_send_pds(struct wfx_dev *wdev, unsigned char *buf, size_t len);
 
 #endif
-- 
2.20.1

^ permalink raw reply related

* [PATCH v2 09/20] staging: wfx: add support for start-up indication
From: Jerome Pouiller @ 2019-09-19 13:52 UTC (permalink / raw)
  To: devel@driverdev.osuosl.org, linux-wireless@vger.kernel.org
  Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	Greg Kroah-Hartman, Kalle Valo, David S . Miller, David Le Goff,
	Jerome Pouiller
In-Reply-To: <20190919135220.30663-1-Jerome.Pouiller@silabs.com>

From: Jérôme Pouiller <jerome.pouiller@silabs.com>

Once firmware is loaded, it send a first indication to host. This
indication signalize that host can start to communicate with firmware.
In add, it contains information about chip and firmware (MAC addresses,
firmware version, etc...).

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
 drivers/staging/wfx/Makefile |  1 +
 drivers/staging/wfx/bh.c     |  4 ++-
 drivers/staging/wfx/hif_rx.c | 57 ++++++++++++++++++++++++++++++++++++
 drivers/staging/wfx/hif_rx.h | 18 ++++++++++++
 drivers/staging/wfx/main.c   | 45 ++++++++++++++++++++++++++++
 drivers/staging/wfx/wfx.h    |  5 ++++
 6 files changed, 129 insertions(+), 1 deletion(-)
 create mode 100644 drivers/staging/wfx/hif_rx.c
 create mode 100644 drivers/staging/wfx/hif_rx.h

diff --git a/drivers/staging/wfx/Makefile b/drivers/staging/wfx/Makefile
index 1abd3115f11d..35670b86c64f 100644
--- a/drivers/staging/wfx/Makefile
+++ b/drivers/staging/wfx/Makefile
@@ -7,6 +7,7 @@ wfx-y := \
 	bh.o \
 	hwio.o \
 	fwio.o \
+	hif_rx.o \
 	main.o \
 	debug.o
 wfx-$(CONFIG_SPI) += bus_spi.o
diff --git a/drivers/staging/wfx/bh.c b/drivers/staging/wfx/bh.c
index 76afecdf579d..c40da3f1f25d 100644
--- a/drivers/staging/wfx/bh.c
+++ b/drivers/staging/wfx/bh.c
@@ -12,6 +12,7 @@
 #include "wfx.h"
 #include "hwio.h"
 #include "traces.h"
+#include "hif_rx.h"
 #include "hif_api_cmd.h"
 
 static void device_wakeup(struct wfx_dev *wdev)
@@ -107,7 +108,8 @@ static int rx_helper(struct wfx_dev *wdev, size_t read_len, int *is_cnf)
 	}
 
 	skb_put(skb, hif->len);
-	dev_kfree_skb(skb); /* FIXME: handle received data */
+	// wfx_handle_rx takes care on SKB livetime
+	wfx_handle_rx(wdev, skb);
 
 	return piggyback;
 
diff --git a/drivers/staging/wfx/hif_rx.c b/drivers/staging/wfx/hif_rx.c
new file mode 100644
index 000000000000..5c207e6d4348
--- /dev/null
+++ b/drivers/staging/wfx/hif_rx.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Implementation of chip-to-host event (aka indications) of WFxxx Split Mac
+ * (WSM) API.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#include <linux/skbuff.h>
+#include <linux/etherdevice.h>
+
+#include "hif_rx.h"
+#include "wfx.h"
+#include "hif_api_cmd.h"
+
+static int hif_startup_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
+{
+	struct hif_ind_startup *body = buf;
+
+	if (body->status || body->firmware_type > 4) {
+		dev_err(wdev->dev, "received invalid startup indication");
+		return -EINVAL;
+	}
+	memcpy(&wdev->hw_caps, body, sizeof(struct hif_ind_startup));
+	le32_to_cpus(&wdev->hw_caps.status);
+	le16_to_cpus(&wdev->hw_caps.hardware_id);
+	le16_to_cpus(&wdev->hw_caps.num_inp_ch_bufs);
+	le16_to_cpus(&wdev->hw_caps.size_inp_ch_buf);
+
+	complete(&wdev->firmware_ready);
+	return 0;
+}
+
+static const struct {
+	int msg_id;
+	int (*handler)(struct wfx_dev *wdev, struct hif_msg *hif, void *buf);
+} hif_handlers[] = {
+	{ HIF_IND_ID_STARTUP,              hif_startup_indication },
+};
+
+void wfx_handle_rx(struct wfx_dev *wdev, struct sk_buff *skb)
+{
+	int i;
+	struct hif_msg *hif = (struct hif_msg *) skb->data;
+	int hif_id = hif->id;
+
+	for (i = 0; i < ARRAY_SIZE(hif_handlers); i++) {
+		if (hif_handlers[i].msg_id == hif_id) {
+			if (hif_handlers[i].handler)
+				hif_handlers[i].handler(wdev, hif, hif->body);
+			goto free;
+		}
+	}
+	dev_err(wdev->dev, "unsupported HIF ID %02x\n", hif_id);
+free:
+	dev_kfree_skb(skb);
+}
diff --git a/drivers/staging/wfx/hif_rx.h b/drivers/staging/wfx/hif_rx.h
new file mode 100644
index 000000000000..f07c10c8c6bd
--- /dev/null
+++ b/drivers/staging/wfx/hif_rx.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Implementation of chip-to-host event (aka indications) of WFxxx Split Mac
+ * (WSM) API.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ * Copyright (C) 2010, ST-Ericsson SA
+ */
+#ifndef WFX_HIF_RX_H
+#define WFX_HIF_RX_H
+
+struct wfx_dev;
+struct sk_buff;
+
+void wfx_handle_rx(struct wfx_dev *wdev, struct sk_buff *skb);
+
+#endif
diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c
index f0bea053a0d9..5e7e7225f068 100644
--- a/drivers/staging/wfx/main.c
+++ b/drivers/staging/wfx/main.c
@@ -12,6 +12,7 @@
  */
 #include <linux/module.h>
 #include <linux/of.h>
+#include <linux/of_net.h>
 #include <linux/gpio.h>
 #include <linux/gpio/consumer.h>
 #include <linux/mmc/sdio_func.h>
@@ -87,6 +88,9 @@ struct wfx_dev *wfx_init_common(struct device *dev,
 	wdev->hwbus_ops = hwbus_ops;
 	wdev->hwbus_priv = hwbus_priv;
 	memcpy(&wdev->pdata, pdata, sizeof(*pdata));
+
+	init_completion(&wdev->firmware_ready);
+
 	return wdev;
 }
 
@@ -96,7 +100,9 @@ void wfx_free_common(struct wfx_dev *wdev)
 
 int wfx_probe(struct wfx_dev *wdev)
 {
+	int i;
 	int err;
+	const void *macaddr;
 
 	wfx_bh_register(wdev);
 
@@ -104,6 +110,45 @@ int wfx_probe(struct wfx_dev *wdev)
 	if (err)
 		goto err1;
 
+	err = wait_for_completion_interruptible_timeout(&wdev->firmware_ready, 10 * HZ);
+	if (err <= 0) {
+		if (err == 0) {
+			dev_err(wdev->dev, "timeout while waiting for startup indication. IRQ configuration error?\n");
+			err = -ETIMEDOUT;
+		} else if (err == -ERESTARTSYS) {
+			dev_info(wdev->dev, "probe interrupted by user\n");
+		}
+		goto err1;
+	}
+
+	// FIXME: fill wiphy::hw_version
+	dev_info(wdev->dev, "started firmware %d.%d.%d \"%s\" (API: %d.%d, keyset: %02X, caps: 0x%.8X)\n",
+		 wdev->hw_caps.firmware_major, wdev->hw_caps.firmware_minor,
+		 wdev->hw_caps.firmware_build, wdev->hw_caps.firmware_label,
+		 wdev->hw_caps.api_version_major, wdev->hw_caps.api_version_minor,
+		 wdev->keyset, *((u32 *) &wdev->hw_caps.capabilities));
+
+	if (wfx_api_older_than(wdev, 1, 0)) {
+		dev_err(wdev->dev, "unsupported firmware API version (expect 1 while firmware returns %d)\n",
+			wdev->hw_caps.api_version_major);
+		err = -ENOTSUPP;
+		goto err1;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(wdev->addresses); i++) {
+		eth_zero_addr(wdev->addresses[i].addr);
+		macaddr = of_get_mac_address(wdev->dev->of_node);
+		if (macaddr) {
+			ether_addr_copy(wdev->addresses[i].addr, macaddr);
+			wdev->addresses[i].addr[ETH_ALEN - 1] += i;
+		}
+		ether_addr_copy(wdev->addresses[i].addr, wdev->hw_caps.mac_addr[i]);
+		if (!is_valid_ether_addr(wdev->addresses[i].addr)) {
+			dev_warn(wdev->dev, "using random MAC address\n");
+			eth_random_addr(wdev->addresses[i].addr);
+		}
+		dev_info(wdev->dev, "MAC address %d: %pM\n", i, wdev->addresses[i].addr);
+	}
 
 	return 0;
 
diff --git a/drivers/staging/wfx/wfx.h b/drivers/staging/wfx/wfx.h
index 4f28938fa3a6..f5f9a337d828 100644
--- a/drivers/staging/wfx/wfx.h
+++ b/drivers/staging/wfx/wfx.h
@@ -10,6 +10,9 @@
 #ifndef WFX_H
 #define WFX_H
 
+#include <linux/completion.h>
+#include <net/mac80211.h>
+
 #include "bh.h"
 #include "main.h"
 #include "hif_api_general.h"
@@ -19,10 +22,12 @@ struct hwbus_ops;
 struct wfx_dev {
 	struct wfx_platform_data pdata;
 	struct device		*dev;
+	struct mac_address	addresses[2];
 	const struct hwbus_ops	*hwbus_ops;
 	void			*hwbus_priv;
 
 	u8			keyset;
+	struct completion	firmware_ready;
 	struct hif_ind_startup	hw_caps;
 	struct wfx_hif		hif;
 };
-- 
2.20.1

^ permalink raw reply related

* [PATCH v2 10/20] staging: wfx: instantiate mac80211 data
From: Jerome Pouiller @ 2019-09-19 13:52 UTC (permalink / raw)
  To: devel@driverdev.osuosl.org, linux-wireless@vger.kernel.org
  Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	Greg Kroah-Hartman, Kalle Valo, David S . Miller, David Le Goff,
	Jerome Pouiller
In-Reply-To: <20190919135220.30663-1-Jerome.Pouiller@silabs.com>

From: Jérôme Pouiller <jerome.pouiller@silabs.com>

Allocate a struct ieee80211_hw but do not yet register it.

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
 drivers/staging/wfx/Makefile |  1 +
 drivers/staging/wfx/debug.c  | 12 ++++++++++
 drivers/staging/wfx/debug.h  | 15 ++++++++++++
 drivers/staging/wfx/main.c   | 41 ++++++++++++++++++++++++++++++--
 drivers/staging/wfx/sta.c    | 46 ++++++++++++++++++++++++++++++++++++
 drivers/staging/wfx/sta.h    | 24 +++++++++++++++++++
 drivers/staging/wfx/wfx.h    |  8 +++++++
 7 files changed, 145 insertions(+), 2 deletions(-)
 create mode 100644 drivers/staging/wfx/debug.h
 create mode 100644 drivers/staging/wfx/sta.c
 create mode 100644 drivers/staging/wfx/sta.h

diff --git a/drivers/staging/wfx/Makefile b/drivers/staging/wfx/Makefile
index 35670b86c64f..2896a2127c88 100644
--- a/drivers/staging/wfx/Makefile
+++ b/drivers/staging/wfx/Makefile
@@ -9,6 +9,7 @@ wfx-y := \
 	fwio.o \
 	hif_rx.o \
 	main.o \
+	sta.o \
 	debug.o
 wfx-$(CONFIG_SPI) += bus_spi.o
 wfx-$(subst m,y,$(CONFIG_MMC)) += bus_sdio.o
diff --git a/drivers/staging/wfx/debug.c b/drivers/staging/wfx/debug.c
index bf44c944640d..f28c94d8de89 100644
--- a/drivers/staging/wfx/debug.c
+++ b/drivers/staging/wfx/debug.c
@@ -5,6 +5,18 @@
  * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
  * Copyright (c) 2010, ST-Ericsson
  */
+#include <linux/debugfs.h>
+
+#include "wfx.h"
 
 #define CREATE_TRACE_POINTS
 #include "traces.h"
+
+int wfx_debug_init(struct wfx_dev *wdev)
+{
+	struct dentry *d;
+
+	d = debugfs_create_dir("wfx", wdev->hw->wiphy->debugfsdir);
+
+	return 0;
+}
diff --git a/drivers/staging/wfx/debug.h b/drivers/staging/wfx/debug.h
new file mode 100644
index 000000000000..8bfba1a9fa20
--- /dev/null
+++ b/drivers/staging/wfx/debug.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Debugfs interface.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2011, ST-Ericsson
+ */
+#ifndef WFX_DEBUG_H
+#define WFX_DEBUG_H
+
+struct wfx_dev;
+
+int wfx_debug_init(struct wfx_dev *wdev);
+
+#endif /* WFX_DEBUG_H */
diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c
index 5e7e7225f068..ca0ca873bd7d 100644
--- a/drivers/staging/wfx/main.c
+++ b/drivers/staging/wfx/main.c
@@ -25,6 +25,9 @@
 #include "hwio.h"
 #include "bus.h"
 #include "bh.h"
+#include "sta.h"
+#include "debug.h"
+#include "hif_api_cmd.h"
 #include "wfx_version.h"
 
 MODULE_DESCRIPTION("Silicon Labs 802.11 Wireless LAN driver for WFx");
@@ -36,6 +39,13 @@ static int gpio_wakeup = -2;
 module_param(gpio_wakeup, int, 0644);
 MODULE_PARM_DESC(gpio_wakeup, "gpio number for wakeup. -1 for none.");
 
+static const struct ieee80211_ops wfx_ops = {
+	.start			= wfx_start,
+	.stop			= wfx_stop,
+	.add_interface		= wfx_add_interface,
+	.remove_interface	= wfx_remove_interface,
+};
+
 bool wfx_api_older_than(struct wfx_dev *wdev, int major, int minor)
 {
 	if (wdev->hw_caps.api_version_major < major)
@@ -79,11 +89,26 @@ struct wfx_dev *wfx_init_common(struct device *dev,
 				const struct hwbus_ops *hwbus_ops,
 				void *hwbus_priv)
 {
+	struct ieee80211_hw *hw;
 	struct wfx_dev *wdev;
 
-	wdev = devm_kmalloc(dev, sizeof(*wdev), GFP_KERNEL);
-	if (!wdev)
+	hw = ieee80211_alloc_hw(sizeof(struct wfx_dev), &wfx_ops);
+	if (!hw)
 		return NULL;
+
+	SET_IEEE80211_DEV(hw, dev);
+
+	hw->vif_data_size = sizeof(struct wfx_vif);
+	hw->sta_data_size = sizeof(struct wfx_sta_priv);
+	hw->queues = 4;
+	hw->max_rates = 8;
+	hw->max_rate_tries = 15;
+	hw->extra_tx_headroom = sizeof(struct hif_sl_msg_hdr) + sizeof(struct hif_msg)
+				+ sizeof(struct hif_req_tx)
+				+ 4 /* alignment */ + 8 /* TKIP IV */;
+
+	wdev = hw->priv;
+	wdev->hw = hw;
 	wdev->dev = dev;
 	wdev->hwbus_ops = hwbus_ops;
 	wdev->hwbus_priv = hwbus_priv;
@@ -96,6 +121,7 @@ struct wfx_dev *wfx_init_common(struct device *dev,
 
 void wfx_free_common(struct wfx_dev *wdev)
 {
+	ieee80211_free_hw(wdev->hw);
 }
 
 int wfx_probe(struct wfx_dev *wdev)
@@ -127,6 +153,11 @@ int wfx_probe(struct wfx_dev *wdev)
 		 wdev->hw_caps.firmware_build, wdev->hw_caps.firmware_label,
 		 wdev->hw_caps.api_version_major, wdev->hw_caps.api_version_minor,
 		 wdev->keyset, *((u32 *) &wdev->hw_caps.capabilities));
+	snprintf(wdev->hw->wiphy->fw_version, sizeof(wdev->hw->wiphy->fw_version),
+		 "%d.%d.%d",
+		 wdev->hw_caps.firmware_major,
+		 wdev->hw_caps.firmware_minor,
+		 wdev->hw_caps.firmware_build);
 
 	if (wfx_api_older_than(wdev, 1, 0)) {
 		dev_err(wdev->dev, "unsupported firmware API version (expect 1 while firmware returns %d)\n",
@@ -150,8 +181,14 @@ int wfx_probe(struct wfx_dev *wdev)
 		dev_info(wdev->dev, "MAC address %d: %pM\n", i, wdev->addresses[i].addr);
 	}
 
+	err = wfx_debug_init(wdev);
+	if (err)
+		goto err2;
+
 	return 0;
 
+err2:
+	ieee80211_free_hw(wdev->hw);
 err1:
 	wfx_bh_unregister(wdev);
 	return err;
diff --git a/drivers/staging/wfx/sta.c b/drivers/staging/wfx/sta.c
new file mode 100644
index 000000000000..fe3ff6536a87
--- /dev/null
+++ b/drivers/staging/wfx/sta.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Implementation of mac80211 API.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#include <net/mac80211.h>
+
+#include "sta.h"
+#include "wfx.h"
+
+int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+	int i;
+	struct wfx_dev *wdev = hw->priv;
+	struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+
+	for (i = 0; i < ARRAY_SIZE(wdev->vif); i++) {
+		if (!wdev->vif[i]) {
+			wdev->vif[i] = vif;
+			wvif->id = i;
+			break;
+		}
+	}
+	if (i == ARRAY_SIZE(wdev->vif))
+		return -EOPNOTSUPP;
+	wvif->vif = vif;
+	wvif->wdev = wdev;
+
+	return 0;
+}
+
+void wfx_remove_interface(struct ieee80211_hw *hw,
+			  struct ieee80211_vif *vif)
+{
+}
+
+int wfx_start(struct ieee80211_hw *hw)
+{
+	return 0;
+}
+
+void wfx_stop(struct ieee80211_hw *hw)
+{
+}
diff --git a/drivers/staging/wfx/sta.h b/drivers/staging/wfx/sta.h
new file mode 100644
index 000000000000..f17b4d1511d7
--- /dev/null
+++ b/drivers/staging/wfx/sta.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Implementation of mac80211 API.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#ifndef WFX_STA_H
+#define WFX_STA_H
+
+#include <net/mac80211.h>
+
+struct wfx_sta_priv {
+	int link_id;
+	int vif_id;
+};
+
+// mac80211 interface
+int wfx_start(struct ieee80211_hw *hw);
+void wfx_stop(struct ieee80211_hw *hw);
+int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
+void wfx_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
+
+#endif /* WFX_STA_H */
diff --git a/drivers/staging/wfx/wfx.h b/drivers/staging/wfx/wfx.h
index f5f9a337d828..a7e571e0da30 100644
--- a/drivers/staging/wfx/wfx.h
+++ b/drivers/staging/wfx/wfx.h
@@ -22,6 +22,8 @@ struct hwbus_ops;
 struct wfx_dev {
 	struct wfx_platform_data pdata;
 	struct device		*dev;
+	struct ieee80211_hw	*hw;
+	struct ieee80211_vif	*vif[2];
 	struct mac_address	addresses[2];
 	const struct hwbus_ops	*hwbus_ops;
 	void			*hwbus_priv;
@@ -32,4 +34,10 @@ struct wfx_dev {
 	struct wfx_hif		hif;
 };
 
+struct wfx_vif {
+	struct wfx_dev		*wdev;
+	struct ieee80211_vif	*vif;
+	int			id;
+};
+
 #endif /* WFX_H */
-- 
2.20.1

^ 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