[haiku-commits] Re: haiku: hrev52067 - data/system/data/firmware/idualwifi7260 src/add-ons/kernel/drivers/network/wlan/idualwifi7260/dev/iwm

  • From: "Simon Taylor" <dmarc-noreply@xxxxxxxxxxxxx> (Redacted sender "simontaylor1" for DMARC)
  • To: haiku-commits@xxxxxxxxxxxxx
  • Date: Thu, 5 Jul 2018 09:21:09 +0100

Congrats Augustin!

I’m still lurking on the Haiku lists and remember you popping up as a 16 year 
old many years ago. Great to see you grow with the project and today to be one 
of the most valuable contributors across the entire codebase.

Simon

On 5 Jul 2018, at 01:51, waddlesplash <waddlesplash@xxxxxxxxx> wrote:

hrev52067 adds 3 changesets to branch 'master'
old head: 6fdf2dd2b3442767d465ceb817f597e5471519fa
new head: e89c61736e5bf1959ad9af1b480cc347ab95674e
overview: 
https://git.haiku-os.org/haiku/log/?qt=range&q=e89c61736e5b+%5E6fdf2dd2b344

----------------------------------------------------------------------------

02463a2c7370: freebsd11_network: Handle NULL gracefully instead of faulting.

 FreeBSD does not have these checks, but drivers seem to expect that they
 can call these functions with NULL and not crash.

 Fixes a number of boot-failure tickets (and makes it possible for me at least
 to test drivers without rebooting from KDL every failure), though of course 
the
 drivers themselves will still not work.

999fe6b3ea2b: freebsd11_network: Additions for idualwifi7260.

e89c61736e5b: drivers/network/wlan: Import idualwifi7260 from FreeBSD 11.2.

 The lowest model number supported by this driver is "3160", but that's just
 Intel's insanity: the 7260 was released the quarter before it. So following
 our naming convention strictly, "7260" is the correct name for this driver.

 The firmware situation for this one is also a little different. Unlike past
 instances where Intel has released mostly nonsubstantial firmware updates,
 allowing us to just copy a recent-ish version from the iwlwifi archives,
 the firmware is more closely tied to the driver in this series. As a result,
 some of this firmware is not even used by Linux yet (they're a few versions
 behind it seems), so the firmware packages included here come from FreeBSD.

 One major hardware feature - RX of multiple frames at a time - is disabled
 in this commit, as it depends on mbuf reference-counting, according to the
 FreeBSD developers I asked, which we do not implement yet. I'll hopefully
 get to looking at that in the next few weeks.

 And with that, I finally have WiFi on my primary laptop, my original reason
 for setting out on this quest last year. This commit was pushed through it,
 even :)

                             [ Augustin Cavalier <waddlesplash@xxxxxxxxx> ]

----------------------------------------------------------------------------

51 files changed, 21287 insertions(+)
build/jam/images/definitions/minimum             |    1 +
.../firmware/idualwifi7260/iwm-3160-ucode-17.tgz |  Bin 0 -> 466733 bytes
.../firmware/idualwifi7260/iwm-7260-ucode-17.tgz |  Bin 0 -> 493347 bytes
.../firmware/idualwifi7260/iwm-7265-ucode-17.tgz |  Bin 0 -> 534927 bytes
.../idualwifi7260/iwm-7265D-ucode-22.tgz         |  Bin 0 -> 430262 bytes
.../idualwifi7260/iwm-8000C-ucode-22.tgz         |  Bin 0 -> 847449 bytes
.../firmware/idualwifi7260/iwm-8265-ucode-22.tgz |  Bin 0 -> 722947 bytes
src/add-ons/kernel/drivers/network/wlan/Jamfile  |    1 +
.../drivers/network/wlan/idualwifi7260/Jamfile   |   49 +
.../network/wlan/idualwifi7260/dev/iwm/if_iwm.c  | 6622 ++++++++++++++++++
.../wlan/idualwifi7260/dev/iwm/if_iwm_7000.c     |  129 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_8000.c     |  103 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_binding.c  |  257 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_binding.h  |  111 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_config.h   |  136 +
.../idualwifi7260/dev/iwm/if_iwm_constants.h     |  154 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_debug.h    |   61 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_fw.c       |  342 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_fw.h       |  113 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_led.c      |  185 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_led.h      |   99 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_mac_ctxt.c |  556 ++
.../wlan/idualwifi7260/dev/iwm/if_iwm_mac_ctxt.h |  115 +
.../idualwifi7260/dev/iwm/if_iwm_notif_wait.c    |  221 +
.../idualwifi7260/dev/iwm/if_iwm_notif_wait.h    |  138 +
.../idualwifi7260/dev/iwm/if_iwm_pcie_trans.c    |  657 ++
.../idualwifi7260/dev/iwm/if_iwm_pcie_trans.h    |  135 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_phy_ctxt.c |  320 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_phy_ctxt.h |  117 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_phy_db.c   |  612 ++
.../wlan/idualwifi7260/dev/iwm/if_iwm_phy_db.h   |  117 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_power.c    |  472 ++
.../wlan/idualwifi7260/dev/iwm/if_iwm_power.h    |  100 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_scan.c     |  772 ++
.../wlan/idualwifi7260/dev/iwm/if_iwm_scan.h     |  114 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_sta.c      |  383 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_sta.h      |  223 +
.../idualwifi7260/dev/iwm/if_iwm_time_event.c    |  213 +
.../idualwifi7260/dev/iwm/if_iwm_time_event.h    |  113 +
.../wlan/idualwifi7260/dev/iwm/if_iwm_util.c     |  530 ++
.../wlan/idualwifi7260/dev/iwm/if_iwm_util.h     |  163 +
.../wlan/idualwifi7260/dev/iwm/if_iwmreg.h       | 6137 ++++++++++++++++
.../wlan/idualwifi7260/dev/iwm/if_iwmvar.h       |  568 ++
.../network/wlan/idualwifi7260/dev/iwm/opt_iwm.h |    0
.../wlan/idualwifi7260/dev/iwm/opt_wlan.h        |    2 +
.../drivers/network/wlan/idualwifi7260/glue.c    |   60 +
src/libs/compat/freebsd11_network/callout.cpp    |    5 +
.../compat/freebsd11_network/compat/sys/mbuf.h   |    4 +
.../compat/freebsd11_network/compat/sys/param.h  |    1 +
src/libs/compat/freebsd11_network/fbsd_mbuf.c    |   71 +
src/libs/compat/freebsd11_network/taskqueue.c    |    5 +

############################################################################

Commit:      02463a2c7370251a412f8b5f1a221ee002681634
URL:         https://git.haiku-os.org/haiku/commit/?id=02463a2c7370
Author:      Augustin Cavalier <waddlesplash@xxxxxxxxx>
Date:        Wed Jul  4 14:16:18 2018 UTC

freebsd11_network: Handle NULL gracefully instead of faulting.

FreeBSD does not have these checks, but drivers seem to expect that they
can call these functions with NULL and not crash.

Fixes a number of boot-failure tickets (and makes it possible for me at least
to test drivers without rebooting from KDL every failure), though of course 
the
drivers themselves will still not work.

----------------------------------------------------------------------------

diff --git a/src/libs/compat/freebsd11_network/callout.cpp 
b/src/libs/compat/freebsd11_network/callout.cpp
index c80938b63e..3548ec8494 100644
--- a/src/libs/compat/freebsd11_network/callout.cpp
+++ b/src/libs/compat/freebsd11_network/callout.cpp
@@ -212,6 +212,11 @@ _callout_stop_safe(struct callout *c, int safe)
{
      MutexLocker locker(sLock);

+     if (c == NULL) {
+             printf("_callout_stop_safe called with NULL callout");
+             return 0;
+     }
+
      TRACE("_callout_stop_safe %p, func %p, arg %p\n", c, c->c_func, 
c->c_arg);

      if (c->due <= 0)
diff --git a/src/libs/compat/freebsd11_network/taskqueue.c 
b/src/libs/compat/freebsd11_network/taskqueue.c
index 94b78a6641..89c7cbefac 100644
--- a/src/libs/compat/freebsd11_network/taskqueue.c
+++ b/src/libs/compat/freebsd11_network/taskqueue.c
@@ -247,6 +247,11 @@ taskqueue_drain(struct taskqueue *taskQueue, struct task 
*task)
{
      cpu_status status;

+     if (taskQueue == NULL) {
+             printf("taskqueue_drain called with NULL taskqueue");
+             return;
+     }
+
      tq_lock(taskQueue, &status);
      while (task->ta_pending != 0) {
              tq_unlock(taskQueue, status);

############################################################################

Commit:      999fe6b3ea2ba8c15ec722ec0aee102084898a8c
URL:         https://git.haiku-os.org/haiku/commit/?id=999fe6b3ea2b
Author:      Augustin Cavalier <waddlesplash@xxxxxxxxx>
Date:        Thu Jul  5 00:34:32 2018 UTC

freebsd11_network: Additions for idualwifi7260.

----------------------------------------------------------------------------

diff --git a/src/libs/compat/freebsd11_network/compat/sys/mbuf.h 
b/src/libs/compat/freebsd11_network/compat/sys/mbuf.h
index 302bdbed08..eb767edfd9 100644
--- a/src/libs/compat/freebsd11_network/compat/sys/mbuf.h
+++ b/src/libs/compat/freebsd11_network/compat/sys/mbuf.h
@@ -81,6 +81,9 @@

#define MTAG_PERSISTENT       0x800

+#define      M_COPYALL       1000000000
+     // Length to m_copy to copy all.
+
#define EXT_CLUSTER           1               // 2048 bytes
#define EXT_JUMBOP            4               // Page size
#define EXT_JUMBO9            5               // 9 * 1024 bytes
@@ -189,6 +192,7 @@ struct mbuf*      m_collapse(struct mbuf*, int, int);
void                  m_copyback(struct mbuf*, int, int, caddr_t);
void                  m_copydata(const struct mbuf*, int, int, caddr_t);
struct mbuf*  m_copypacket(struct mbuf*, int);
+struct mbuf *        m_copym(struct mbuf *m, int off0, int len, int wait);
struct mbuf*  m_defrag(struct mbuf*, int);
struct mbuf*  m_devget(char*, int, int, struct ifnet*,
      void(*) (char*, caddr_t, u_int));
diff --git a/src/libs/compat/freebsd11_network/compat/sys/param.h 
b/src/libs/compat/freebsd11_network/compat/sys/param.h
index 36bdd29c54..336ad4cbcd 100644
--- a/src/libs/compat/freebsd11_network/compat/sys/param.h
+++ b/src/libs/compat/freebsd11_network/compat/sys/param.h
@@ -64,6 +64,7 @@
#define roundup(x, y) ((((x)+((y)-1))/(y))*(y))  /* to any y */
#define roundup2(x, y)        (((x) + ((y) - 1)) & (~((y) - 1)))
#define rounddown(x, y)  (((x) / (y)) * (y))
+#define rounddown2(x, y) ((x)&(~((y)-1)))          /* if y is power of two */

#define       PRIMASK 0x0ff
#define       PCATCH  0x100
diff --git a/src/libs/compat/freebsd11_network/fbsd_mbuf.c 
b/src/libs/compat/freebsd11_network/fbsd_mbuf.c
index ca0cc819a8..443603abae 100644
--- a/src/libs/compat/freebsd11_network/fbsd_mbuf.c
+++ b/src/libs/compat/freebsd11_network/fbsd_mbuf.c
@@ -662,6 +662,77 @@ mb_dupcl(struct mbuf *n, struct mbuf *m)
      n->m_flags |= M_EXT;
}

+/*
+ * Make a copy of an mbuf chain starting "off0" bytes from the beginning,
+ * continuing for "len" bytes.  If len is M_COPYALL, copy to end of mbuf.
+ * The wait parameter is a choice of M_WAITOK/M_NOWAIT from caller.
+ * Note that the copy is read-only, because clusters are not copied,
+ * only their reference counts are incremented.
+ */
+struct mbuf *
+m_copym(struct mbuf *m, int off0, int len, int wait)
+{
+     struct mbuf *n, **np;
+     int off = off0;
+     struct mbuf *top;
+     int copyhdr = 0;
+
+     KASSERT(off >= 0, ("m_copym, negative off %d", off));
+     KASSERT(len >= 0, ("m_copym, negative len %d", len));
+     MBUF_CHECKSLEEP(wait);
+     if (off == 0 && m->m_flags & M_PKTHDR)
+             copyhdr = 1;
+     while (off > 0) {
+             KASSERT(m != NULL, ("m_copym, offset > size of mbuf chain"));
+             if (off < m->m_len)
+                     break;
+             off -= m->m_len;
+             m = m->m_next;
+     }
+     np = &top;
+     top = NULL;
+     while (len > 0) {
+             if (m == NULL) {
+                     KASSERT(len == M_COPYALL,
+                             ("m_copym, length > size of mbuf chain"));
+                     break;
+             }
+             if (copyhdr)
+                     n = m_gethdr(wait, m->m_type);
+             else
+                     n = m_get(wait, m->m_type);
+             *np = n;
+             if (n == NULL)
+                     goto nospace;
+             if (copyhdr) {
+                     if (!m_dup_pkthdr(n, m, wait))
+                             goto nospace;
+                     if (len == M_COPYALL)
+                             n->m_pkthdr.len -= off0;
+                     else
+                             n->m_pkthdr.len = len;
+                     copyhdr = 0;
+             }
+             n->m_len = min(len, m->m_len - off);
+             if (m->m_flags & M_EXT) {
+                     n->m_data = m->m_data + off;
+                     mb_dupcl(n, m);
+             } else
+                     bcopy(mtod(m, caddr_t)+off, mtod(n, caddr_t),
+                             (u_int)n->m_len);
+             if (len != M_COPYALL)
+                     len -= n->m_len;
+             off = 0;
+             m = m->m_next;
+             np = &n->m_next;
+     }
+
+     return (top);
+nospace:
+     m_freem(top);
+     return (NULL);
+}
+
void
m_demote_pkthdr(struct mbuf *m)
{

############################################################################

Revision:    hrev52067
Commit:      e89c61736e5bf1959ad9af1b480cc347ab95674e
URL:         https://git.haiku-os.org/haiku/commit/?id=e89c61736e5b
Author:      Augustin Cavalier <waddlesplash@xxxxxxxxx>
Date:        Thu Jul  5 00:35:15 2018 UTC

drivers/network/wlan: Import idualwifi7260 from FreeBSD 11.2.

The lowest model number supported by this driver is "3160", but that's just
Intel's insanity: the 7260 was released the quarter before it. So following
our naming convention strictly, "7260" is the correct name for this driver.

The firmware situation for this one is also a little different. Unlike past
instances where Intel has released mostly nonsubstantial firmware updates,
allowing us to just copy a recent-ish version from the iwlwifi archives,
the firmware is more closely tied to the driver in this series. As a result,
some of this firmware is not even used by Linux yet (they're a few versions
behind it seems), so the firmware packages included here come from FreeBSD.

One major hardware feature - RX of multiple frames at a time - is disabled
in this commit, as it depends on mbuf reference-counting, according to the
FreeBSD developers I asked, which we do not implement yet. I'll hopefully
get to looking at that in the next few weeks.

And with that, I finally have WiFi on my primary laptop, my original reason
for setting out on this quest last year. This commit was pushed through it,
even :)

----------------------------------------------------------------------------

diff --git a/build/jam/images/definitions/minimum 
b/build/jam/images/definitions/minimum
index 696b13956d..794d0f4d7e 100644
--- a/build/jam/images/definitions/minimum
+++ b/build/jam/images/definitions/minimum
@@ -204,6 +204,7 @@ SYSTEM_ADD_ONS_DRIVERS_NET = [ FFilterByBuildFeatures
              aironetwifi atheroswifi
              broadcom43xx
              iprowifi2100 iprowifi2200 iprowifi3945 iprowifi4965
+             idualwifi7260
              marvell88w8363 marvell88w8335
              ralinkwifi
              wavelanwifi
diff --git a/data/system/data/firmware/idualwifi7260/iwm-3160-ucode-17.tgz 
b/data/system/data/firmware/idualwifi7260/iwm-3160-ucode-17.tgz
new file mode 100644
index 0000000000..755fbf3f93
Binary files /dev/null and 
b/data/system/data/firmware/idualwifi7260/iwm-3160-ucode-17.tgz differ
diff --git a/data/system/data/firmware/idualwifi7260/iwm-7260-ucode-17.tgz 
b/data/system/data/firmware/idualwifi7260/iwm-7260-ucode-17.tgz
new file mode 100644
index 0000000000..dfd1c2d4c8
Binary files /dev/null and 
b/data/system/data/firmware/idualwifi7260/iwm-7260-ucode-17.tgz differ
diff --git a/data/system/data/firmware/idualwifi7260/iwm-7265-ucode-17.tgz 
b/data/system/data/firmware/idualwifi7260/iwm-7265-ucode-17.tgz
new file mode 100644
index 0000000000..7a1f246834
Binary files /dev/null and 
b/data/system/data/firmware/idualwifi7260/iwm-7265-ucode-17.tgz differ
diff --git a/data/system/data/firmware/idualwifi7260/iwm-7265D-ucode-22.tgz 
b/data/system/data/firmware/idualwifi7260/iwm-7265D-ucode-22.tgz
new file mode 100644
index 0000000000..923318beed
Binary files /dev/null and 
b/data/system/data/firmware/idualwifi7260/iwm-7265D-ucode-22.tgz differ
diff --git a/data/system/data/firmware/idualwifi7260/iwm-8000C-ucode-22.tgz 
b/data/system/data/firmware/idualwifi7260/iwm-8000C-ucode-22.tgz
new file mode 100644
index 0000000000..50b837b31b
Binary files /dev/null and 
b/data/system/data/firmware/idualwifi7260/iwm-8000C-ucode-22.tgz differ
diff --git a/data/system/data/firmware/idualwifi7260/iwm-8265-ucode-22.tgz 
b/data/system/data/firmware/idualwifi7260/iwm-8265-ucode-22.tgz
new file mode 100644
index 0000000000..e2b2f67fd9
Binary files /dev/null and 
b/data/system/data/firmware/idualwifi7260/iwm-8265-ucode-22.tgz differ
diff --git a/src/add-ons/kernel/drivers/network/wlan/Jamfile 
b/src/add-ons/kernel/drivers/network/wlan/Jamfile
index 1aa8cb9560..08188ca7ca 100644
--- a/src/add-ons/kernel/drivers/network/wlan/Jamfile
+++ b/src/add-ons/kernel/drivers/network/wlan/Jamfile
@@ -19,3 +19,4 @@ SubInclude HAIKU_TOP src add-ons kernel drivers network 
wlan wavelanwifi ;
# FreeBSD 11.1 drivers
SubInclude HAIKU_TOP src add-ons kernel drivers network wlan iprowifi3945 ;
SubInclude HAIKU_TOP src add-ons kernel drivers network wlan iprowifi4965 ;
+SubInclude HAIKU_TOP src add-ons kernel drivers network wlan idualwifi7260 ;
diff --git a/src/add-ons/kernel/drivers/network/wlan/idualwifi7260/Jamfile 
b/src/add-ons/kernel/drivers/network/wlan/idualwifi7260/Jamfile
new file mode 100644
index 0000000000..19466a03ab
--- /dev/null
+++ b/src/add-ons/kernel/drivers/network/wlan/idualwifi7260/Jamfile
@@ -0,0 +1,49 @@
+SubDir HAIKU_TOP src add-ons kernel drivers network wlan idualwifi7260 ;
+
+UseHeaders [ FDirName $(HAIKU_TOP) src libs compat freebsd11_network compat ]
+     : true ;
+UseHeaders [ FDirName $(HAIKU_TOP) src libs compat freebsd11_wlan ] : true ;
+UsePrivateHeaders net system ;
+UsePrivateKernelHeaders ;
+
+SubDirCcFlags [ FDefines _KERNEL=1 FBSD_DRIVER=1 _XOPEN_SOURCE ]
+     -Wno-format
+     -Wno-unused
+     -Wno-uninitialized ;
+
+UseHeaders [ FDirName $(SUBDIR) ] : true ;
+
+SEARCH_SOURCE += [ FDirName $(SUBDIR) dev iwm ] ;
+
+KernelAddon idualwifi7260 :
+    if_iwm_7000.c
+     if_iwm_8000.c
+     if_iwm_binding.c
+     if_iwm_fw.c
+     if_iwm_led.c
+     if_iwm_mac_ctxt.c
+     if_iwm_notif_wait.c
+     if_iwm_pcie_trans.c
+     if_iwm_phy_ctxt.c
+     if_iwm_phy_db.c
+     if_iwm_power.c
+     if_iwm_scan.c
+     if_iwm_sta.c
+     if_iwm_time_event.c
+     if_iwm_util.c
+     if_iwm.c
+     glue.c
+     :
+     libfreebsd11_wlan.a
+     libfreebsd11_network.a
+     ;
+
+HAIKU_WIFI_FIRMWARE_PACKAGES on idualwifi7260 =
+     iwm-3160-ucode-17
+     iwm-7260-ucode-17 iwm-7265-ucode-17 iwm-7265D-ucode-22
+     iwm-8000C-ucode-22 iwm-8265-ucode-22 ;
+HAIKU_WIFI_FIRMWARE_ARCHIVES on idualwifi7260 =
+     iwm-3160-ucode-17.tgz
+     iwm-7260-ucode-17.tgz iwm-7265-ucode-17.tgz iwm-7265D-ucode-22.tgz
+     iwm-8000C-ucode-22.tgz iwm-8265-ucode-22.tgz ;
+HAIKU_WIFI_FIRMWARE_DO_EXTRACT on idualwifi7260 = true ;
diff --git 
a/src/add-ons/kernel/drivers/network/wlan/idualwifi7260/dev/iwm/if_iwm.c 
b/src/add-ons/kernel/drivers/network/wlan/idualwifi7260/dev/iwm/if_iwm.c
new file mode 100644
index 0000000000..d3294afe4a
--- /dev/null
+++ b/src/add-ons/kernel/drivers/network/wlan/idualwifi7260/dev/iwm/if_iwm.c
@@ -0,0 +1,6622 @@
+/*   $OpenBSD: if_iwm.c,v 1.167 2017/04/04 00:40:52 claudio Exp $    */
+
+/*
+ * Copyright (c) 2014 genua mbh <info@xxxxxxxx>
+ * Copyright (c) 2014 Fixup Software Ltd.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*-
+ * Based on BSD-licensed source modules in the Linux iwlwifi driver,
+ * which were used as the reference documentation for this implementation.
+ *
+ * Driver version we are currently based off of is
+ * Linux 3.14.3 (tag id a2df521e42b1d9a23f620ac79dbfe8655a8391dd)
+ *
+ ***********************************************************************
+ *
+ * This file is provided under a dual BSD/GPLv2 license.  When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110,
+ * USA
+ *
+ * The full GNU General Public License is included in this distribution
+ * in the file called COPYING.
+ *
+ * Contact Information:
+ *  Intel Linux Wireless <ilw@xxxxxxxxxxxxxxx>
+ * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
+ *
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *  * Neither the name Intel Corporation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 2007-2010 Damien Bergamini <damien.bergamini@xxxxxxx>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: releng/11.2/sys/dev/iwm/if_iwm.c 330784 2018-03-11 
22:49:46Z eadler $");
+
+#include "opt_wlan.h"
+#include "opt_iwm.h"
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/endian.h>
+#include <sys/firmware.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/mutex.h>
+#include <sys/module.h>
+#include <sys/proc.h>
+#include <sys/rman.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/linker.h>
+
+#include <machine/bus.h>
+#include <machine/endian.h>
+#include <machine/resource.h>
+
+#include <dev/pci/pcivar.h>
+#include <dev/pci/pcireg.h>
+
+#include <net/bpf.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_arp.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/if_ether.h>
+#include <netinet/ip.h>
+
+#include <net80211/ieee80211_var.h>
+#include <net80211/ieee80211_regdomain.h>
+#include <net80211/ieee80211_ratectl.h>
+#include <net80211/ieee80211_radiotap.h>
+
+#include <dev/iwm/if_iwmreg.h>
+#include <dev/iwm/if_iwmvar.h>
+#include <dev/iwm/if_iwm_config.h>
+#include <dev/iwm/if_iwm_debug.h>
+#include <dev/iwm/if_iwm_notif_wait.h>
+#include <dev/iwm/if_iwm_util.h>
+#include <dev/iwm/if_iwm_binding.h>
+#include <dev/iwm/if_iwm_phy_db.h>
+#include <dev/iwm/if_iwm_mac_ctxt.h>
+#include <dev/iwm/if_iwm_phy_ctxt.h>
+#include <dev/iwm/if_iwm_time_event.h>
+#include <dev/iwm/if_iwm_power.h>
+#include <dev/iwm/if_iwm_scan.h>
+#include <dev/iwm/if_iwm_sta.h>
+
+#include <dev/iwm/if_iwm_pcie_trans.h>
+#include <dev/iwm/if_iwm_led.h>
+#include <dev/iwm/if_iwm_fw.h>
+
+/* From DragonflyBSD */
+#define mtodoff(m, t, off)      ((t)((m)->m_data + (off)))
+
+const uint8_t iwm_nvm_channels[] = {
+     /* 2.4 GHz */
+     1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+     /* 5 GHz */
+     36, 40, 44, 48, 52, 56, 60, 64,
+     100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144,
+     149, 153, 157, 161, 165
+};
+_Static_assert(nitems(iwm_nvm_channels) <= IWM_NUM_CHANNELS,
+    "IWM_NUM_CHANNELS is too small");
+
+const uint8_t iwm_nvm_channels_8000[] = {
+     /* 2.4 GHz */
+     1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+     /* 5 GHz */
+     36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92,
+     96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144,
+     149, 153, 157, 161, 165, 169, 173, 177, 181
+};
+_Static_assert(nitems(iwm_nvm_channels_8000) <= IWM_NUM_CHANNELS_8000,
+    "IWM_NUM_CHANNELS_8000 is too small");
+
+#define IWM_NUM_2GHZ_CHANNELS        14
+#define IWM_N_HW_ADDR_MASK   0xF
+
+/*
+ * XXX For now, there's simply a fixed set of rate table entries
+ * that are populated.
+ */
+const struct iwm_rate {
+     uint8_t rate;
+     uint8_t plcp;
+} iwm_rates[] = {
+     {   2,  IWM_RATE_1M_PLCP  },
+     {   4,  IWM_RATE_2M_PLCP  },
+     {  11,  IWM_RATE_5M_PLCP  },
+     {  22,  IWM_RATE_11M_PLCP },
+     {  12,  IWM_RATE_6M_PLCP  },
+     {  18,  IWM_RATE_9M_PLCP  },
+     {  24,  IWM_RATE_12M_PLCP },
+     {  36,  IWM_RATE_18M_PLCP },
+     {  48,  IWM_RATE_24M_PLCP },
+     {  72,  IWM_RATE_36M_PLCP },
+     {  96,  IWM_RATE_48M_PLCP },
+     { 108,  IWM_RATE_54M_PLCP },
+};
+#define IWM_RIDX_CCK 0
+#define IWM_RIDX_OFDM        4
+#define IWM_RIDX_MAX (nitems(iwm_rates)-1)
+#define IWM_RIDX_IS_CCK(_i_) ((_i_) < IWM_RIDX_OFDM)
+#define IWM_RIDX_IS_OFDM(_i_) ((_i_) >= IWM_RIDX_OFDM)
+
+struct iwm_nvm_section {
+     uint16_t length;
+     uint8_t *data;
+};
+
+#define IWM_MVM_UCODE_ALIVE_TIMEOUT  (10*hz)
+#define IWM_MVM_UCODE_CALIB_TIMEOUT  (2*hz)
+
+struct iwm_mvm_alive_data {
+     int valid;
+     uint32_t scd_base_addr;
+};
+
+static int   iwm_store_cscheme(struct iwm_softc *, const uint8_t *, size_t);
+static int   iwm_firmware_store_section(struct iwm_softc *,
+                                           enum iwm_ucode_type,
+                                           const uint8_t *, size_t);
+static int   iwm_set_default_calib(struct iwm_softc *, const void *);
+static void  iwm_fw_info_free(struct iwm_fw_info *);
+static int   iwm_read_firmware(struct iwm_softc *, enum iwm_ucode_type);
+static int   iwm_alloc_fwmem(struct iwm_softc *);
+static int   iwm_alloc_sched(struct iwm_softc *);
+static int   iwm_alloc_kw(struct iwm_softc *);
+static int   iwm_alloc_ict(struct iwm_softc *);
+static int   iwm_alloc_rx_ring(struct iwm_softc *, struct iwm_rx_ring *);
+static void  iwm_reset_rx_ring(struct iwm_softc *, struct iwm_rx_ring *);
+static void  iwm_free_rx_ring(struct iwm_softc *, struct iwm_rx_ring *);
+static int   iwm_alloc_tx_ring(struct iwm_softc *, struct iwm_tx_ring *,
+                                  int);
+static void  iwm_reset_tx_ring(struct iwm_softc *, struct iwm_tx_ring *);
+static void  iwm_free_tx_ring(struct iwm_softc *, struct iwm_tx_ring *);
+static void  iwm_enable_interrupts(struct iwm_softc *);
+static void  iwm_restore_interrupts(struct iwm_softc *);
+static void  iwm_disable_interrupts(struct iwm_softc *);
+static void  iwm_ict_reset(struct iwm_softc *);
+static int   iwm_allow_mcast(struct ieee80211vap *, struct iwm_softc *);
+static void  iwm_stop_device(struct iwm_softc *);
+static void  iwm_mvm_nic_config(struct iwm_softc *);
+static int   iwm_nic_rx_init(struct iwm_softc *);
+static int   iwm_nic_tx_init(struct iwm_softc *);
+static int   iwm_nic_init(struct iwm_softc *);
+static int   iwm_trans_pcie_fw_alive(struct iwm_softc *, uint32_t);
+static int   iwm_nvm_read_chunk(struct iwm_softc *, uint16_t, uint16_t,
+                                   uint16_t, uint8_t *, uint16_t *);
+static int   iwm_nvm_read_section(struct iwm_softc *, uint16_t, uint8_t *,
+                                  uint16_t *, uint32_t);
+static uint32_t      iwm_eeprom_channel_flags(uint16_t);
+static void  iwm_add_channel_band(struct iwm_softc *,
+                 struct ieee80211_channel[], int, int *, int, size_t,
+                 const uint8_t[]);
+static void  iwm_init_channel_map(struct ieee80211com *, int, int *,
+                 struct ieee80211_channel[]);
+static struct iwm_nvm_data *
+     iwm_parse_nvm_data(struct iwm_softc *, const uint16_t *,
+                        const uint16_t *, const uint16_t *,
+                        const uint16_t *, const uint16_t *,
+                        const uint16_t *);
+static void  iwm_free_nvm_data(struct iwm_nvm_data *);
+static void  iwm_set_hw_address_family_8000(struct iwm_softc *,
+                                            struct iwm_nvm_data *,
+                                            const uint16_t *,
+                                            const uint16_t *);
+static int   iwm_get_sku(const struct iwm_softc *, const uint16_t *,
+                         const uint16_t *);
+static int   iwm_get_nvm_version(const struct iwm_softc *, const uint16_t *);
+static int   iwm_get_radio_cfg(const struct iwm_softc *, const uint16_t *,
+                               const uint16_t *);
+static int   iwm_get_n_hw_addrs(const struct iwm_softc *,
+                                const uint16_t *);
+static void  iwm_set_radio_cfg(const struct iwm_softc *,
+                               struct iwm_nvm_data *, uint32_t);
+static struct iwm_nvm_data *
+     iwm_parse_nvm_sections(struct iwm_softc *, struct iwm_nvm_section *);
+static int   iwm_nvm_init(struct iwm_softc *);
+static int   iwm_pcie_load_section(struct iwm_softc *, uint8_t,
+                                   const struct iwm_fw_desc *);
+static int   iwm_pcie_load_firmware_chunk(struct iwm_softc *, uint32_t,
+                                          bus_addr_t, uint32_t);
+static int   iwm_pcie_load_cpu_sections_8000(struct iwm_softc *sc,
+                                             const struct iwm_fw_sects *,
+                                             int, int *);
+static int   iwm_pcie_load_cpu_sections(struct iwm_softc *,
+                                        const struct iwm_fw_sects *,
+                                        int, int *);
+static int   iwm_pcie_load_given_ucode_8000(struct iwm_softc *,
+                                            const struct iwm_fw_sects *);
+static int   iwm_pcie_load_given_ucode(struct iwm_softc *,
+                                       const struct iwm_fw_sects *);
+static int   iwm_start_fw(struct iwm_softc *, const struct iwm_fw_sects *);
+static int   iwm_send_tx_ant_cfg(struct iwm_softc *, uint8_t);
+static int   iwm_send_phy_cfg_cmd(struct iwm_softc *);
+static int   iwm_mvm_load_ucode_wait_alive(struct iwm_softc *,
+                                              enum iwm_ucode_type);
+static int   iwm_run_init_mvm_ucode(struct iwm_softc *, int);
+static int   iwm_rx_addbuf(struct iwm_softc *, int, int);
+static int   iwm_mvm_get_signal_strength(struct iwm_softc *,
+                                         struct iwm_rx_phy_info *);
+static void  iwm_mvm_rx_rx_phy_cmd(struct iwm_softc *,
+                                      struct iwm_rx_packet *);
+static int   iwm_get_noise(struct iwm_softc *,
+                 const struct iwm_mvm_statistics_rx_non_phy *);
+static void  iwm_mvm_handle_rx_statistics(struct iwm_softc *,
+                 struct iwm_rx_packet *);
+static boolean_t iwm_mvm_rx_rx_mpdu(struct iwm_softc *, struct mbuf *,
+                                 uint32_t, boolean_t);
+static int   iwm_mvm_rx_tx_cmd_single(struct iwm_softc *,
+                                         struct iwm_rx_packet *,
+                                      struct iwm_node *);
+static void  iwm_mvm_rx_tx_cmd(struct iwm_softc *, struct iwm_rx_packet *);
+static void  iwm_cmd_done(struct iwm_softc *, struct iwm_rx_packet *);
+#if 0
+static void  iwm_update_sched(struct iwm_softc *, int, int, uint8_t,
+                                 uint16_t);
+#endif
+static const struct iwm_rate *
+     iwm_tx_fill_cmd(struct iwm_softc *, struct iwm_node *,
+                     struct mbuf *, struct iwm_tx_cmd *);
+static int   iwm_tx(struct iwm_softc *, struct mbuf *,
+                       struct ieee80211_node *, int);
+static int   iwm_raw_xmit(struct ieee80211_node *, struct mbuf *,
+                          const struct ieee80211_bpf_params *);
+static int   iwm_mvm_update_quotas(struct iwm_softc *, struct iwm_vap *);
+static int   iwm_auth(struct ieee80211vap *, struct iwm_softc *);
+static int   iwm_assoc(struct ieee80211vap *, struct iwm_softc *);
+static int   iwm_release(struct iwm_softc *, struct iwm_node *);
+static struct ieee80211_node *
+             iwm_node_alloc(struct ieee80211vap *,
+                            const uint8_t[IEEE80211_ADDR_LEN]);
+static void  iwm_setrates(struct iwm_softc *, struct iwm_node *);
+static int   iwm_media_change(struct ifnet *);
+static int   iwm_newstate(struct ieee80211vap *, enum ieee80211_state, int);
+static void  iwm_endscan_cb(void *, int);
+static void  iwm_mvm_fill_sf_command(struct iwm_softc *,
+                                     struct iwm_sf_cfg_cmd *,
+                                     struct ieee80211_node *);
+static int   iwm_mvm_sf_config(struct iwm_softc *, enum iwm_sf_state);
+static int   iwm_send_bt_init_conf(struct iwm_softc *);
+static boolean_t iwm_mvm_is_lar_supported(struct iwm_softc *);
+static boolean_t iwm_mvm_is_wifi_mcc_supported(struct iwm_softc *);
+static int   iwm_send_update_mcc_cmd(struct iwm_softc *, const char *);
+static void  iwm_mvm_tt_tx_backoff(struct iwm_softc *, uint32_t);
+static int   iwm_init_hw(struct iwm_softc *);
+static void  iwm_init(struct iwm_softc *);
+static void  iwm_start(struct iwm_softc *);
+static void  iwm_stop(struct iwm_softc *);
+static void  iwm_watchdog(void *);
+static void  iwm_parent(struct ieee80211com *);
+#ifdef IWM_DEBUG
+static const char *
+             iwm_desc_lookup(uint32_t);
+static void  iwm_nic_error(struct iwm_softc *);
+static void  iwm_nic_umac_error(struct iwm_softc *);
+#endif
+static void  iwm_handle_rxb(struct iwm_softc *, struct mbuf *);
+static void  iwm_notif_intr(struct iwm_softc *);
+static void  iwm_intr(void *);
+static int   iwm_attach(device_t);
+static int   iwm_is_valid_ether_addr(uint8_t *);
+static void  iwm_preinit(void *);
+static int   iwm_detach_local(struct iwm_softc *sc, int);
+static void  iwm_init_task(void *);
+static void  iwm_radiotap_attach(struct iwm_softc *);
+static struct ieee80211vap *
+             iwm_vap_create(struct ieee80211com *,
+                            const char [IFNAMSIZ], int,
+                            enum ieee80211_opmode, int,
+                            const uint8_t [IEEE80211_ADDR_LEN],
+                            const uint8_t [IEEE80211_ADDR_LEN]);
+static void  iwm_vap_delete(struct ieee80211vap *);
+static void  iwm_xmit_queue_drain(struct iwm_softc *);
+static void  iwm_scan_start(struct ieee80211com *);
+static void  iwm_scan_end(struct ieee80211com *);
+static void  iwm_update_mcast(struct ieee80211com *);
+static void  iwm_set_channel(struct ieee80211com *);
+static void  iwm_scan_curchan(struct ieee80211_scan_state *, unsigned long);
+static void  iwm_scan_mindwell(struct ieee80211_scan_state *);
+static int   iwm_detach(device_t);
+
+static int   iwm_lar_disable = 0;
+TUNABLE_INT("hw.iwm.lar.disable", &iwm_lar_disable);
+
+/*
+ * Firmware parser.
+ */
+
+static int
+iwm_store_cscheme(struct iwm_softc *sc, const uint8_t *data, size_t dlen)
+{
+     const struct iwm_fw_cscheme_list *l = (const void *)data;
+
+     if (dlen < sizeof(*l) ||
+         dlen < sizeof(l->size) + l->size * sizeof(*l->cs))
+             return EINVAL;
+
+     /* we don't actually store anything for now, always use s/w crypto */
+
+     return 0;
+}
+
+static int
+iwm_firmware_store_section(struct iwm_softc *sc,
+    enum iwm_ucode_type type, const uint8_t *data, size_t dlen)
+{
+     struct iwm_fw_sects *fws;
+     struct iwm_fw_desc *fwone;
+
+     if (type >= IWM_UCODE_TYPE_MAX)
+             return EINVAL;
+     if (dlen < sizeof(uint32_t))
+             return EINVAL;
+
+     fws = &sc->sc_fw.fw_sects[type];
+     if (fws->fw_count >= IWM_UCODE_SECTION_MAX)
+             return EINVAL;
+
+     fwone = &fws->fw_sect[fws->fw_count];
+
+     /* first 32bit are device load offset */
+     memcpy(&fwone->offset, data, sizeof(uint32_t));
+
+     /* rest is data */
+     fwone->data = data + sizeof(uint32_t);
+     fwone->len = dlen - sizeof(uint32_t);
+
+     fws->fw_count++;
+
+     return 0;
+}
+
+#define IWM_DEFAULT_SCAN_CHANNELS 40
+
+/* iwlwifi: iwl-drv.c */
+struct iwm_tlv_calib_data {
+     uint32_t ucode_type;
+     struct iwm_tlv_calib_ctrl calib;
+} __packed;
+
+static int
+iwm_set_default_calib(struct iwm_softc *sc, const void *data)
+{
+     const struct iwm_tlv_calib_data *def_calib = data;
+     uint32_t ucode_type = le32toh(def_calib->ucode_type);
+
+     if (ucode_type >= IWM_UCODE_TYPE_MAX) {
+             device_printf(sc->sc_dev,
+                 "Wrong ucode_type %u for default "
+                 "calibration.\n", ucode_type);
+             return EINVAL;
+     }
+
+     sc->sc_default_calib[ucode_type].flow_trigger =
+         def_calib->calib.flow_trigger;
+     sc->sc_default_calib[ucode_type].event_trigger =
+         def_calib->calib.event_trigger;
+
+     return 0;
+}
+
+static int
+iwm_set_ucode_api_flags(struct iwm_softc *sc, const uint8_t *data,
+                     struct iwm_ucode_capabilities *capa)
+{
+     const struct iwm_ucode_api *ucode_api = (const void *)data;
+     uint32_t api_index = le32toh(ucode_api->api_index);
+     uint32_t api_flags = le32toh(ucode_api->api_flags);
+     int i;
+
+     if (api_index >= howmany(IWM_NUM_UCODE_TLV_API, 32)) {
+             device_printf(sc->sc_dev,
+                 "api flags index %d larger than supported by driver\n",
+                 api_index);
+             /* don't return an error so we can load FW that has more bits */
+             return 0;
+     }
+
+     for (i = 0; i < 32; i++) {
+             if (api_flags & (1U << i))
+                     setbit(capa->enabled_api, i + 32 * api_index);
+     }
+
+     return 0;
+}
+
+static int
+iwm_set_ucode_capabilities(struct iwm_softc *sc, const uint8_t *data,
+                        struct iwm_ucode_capabilities *capa)
+{
+     const struct iwm_ucode_capa *ucode_capa = (const void *)data;
+     uint32_t api_index = le32toh(ucode_capa->api_index);
+     uint32_t api_flags = le32toh(ucode_capa->api_capa);
+     int i;
+
+     if (api_index >= howmany(IWM_NUM_UCODE_TLV_CAPA, 32)) {
+             device_printf(sc->sc_dev,
+                 "capa flags index %d larger than supported by driver\n",
+                 api_index);
+             /* don't return an error so we can load FW that has more bits */
+             return 0;
+     }
+
+     for (i = 0; i < 32; i++) {
+             if (api_flags & (1U << i))
+                     setbit(capa->enabled_capa, i + 32 * api_index);
+     }
+
+     return 0;
+}
+
+static void
+iwm_fw_info_free(struct iwm_fw_info *fw)
+{
+     firmware_put(fw->fw_fp, FIRMWARE_UNLOAD);
+     fw->fw_fp = NULL;
+     /* don't touch fw->fw_status */
+     memset(fw->fw_sects, 0, sizeof(fw->fw_sects));
+}
+
+static int
+iwm_read_firmware(struct iwm_softc *sc, enum iwm_ucode_type ucode_type)
+{
+     struct iwm_fw_info *fw = &sc->sc_fw;
+     const struct iwm_tlv_ucode_header *uhdr;
+     struct iwm_ucode_tlv tlv;
+     struct iwm_ucode_capabilities *capa = &sc->ucode_capa;
+     enum iwm_ucode_tlv_type tlv_type;
+     const struct firmware *fwp;
+     const uint8_t *data;
+     uint32_t usniffer_img;
+     uint32_t paging_mem_size;
+     int num_of_cpus;
+     int error = 0;
+     size_t len;
+
+     if (fw->fw_status == IWM_FW_STATUS_DONE &&
+         ucode_type != IWM_UCODE_INIT)
+             return 0;
+
+     while (fw->fw_status == IWM_FW_STATUS_INPROGRESS)
+             msleep(&sc->sc_fw, &sc->sc_mtx, 0, "iwmfwp", 0);
+     fw->fw_status = IWM_FW_STATUS_INPROGRESS;
+
+     if (fw->fw_fp != NULL)
+             iwm_fw_info_free(fw);
+
+     /*
+      * Load firmware into driver memory.
+      * fw_fp will be set.
+      */
+     IWM_UNLOCK(sc);
+     fwp = firmware_get(sc->cfg->fw_name);
+     IWM_LOCK(sc);
+     if (fwp == NULL) {
+             device_printf(sc->sc_dev,
+                 "could not read firmware %s (error %d)\n",
+                 sc->cfg->fw_name, error);
+             goto out;
+     }
+     fw->fw_fp = fwp;
+
+     /* (Re-)Initialize default values. */
+     capa->flags = 0;
+     capa->max_probe_length = IWM_DEFAULT_MAX_PROBE_LENGTH;
+     capa->n_scan_channels = IWM_DEFAULT_SCAN_CHANNELS;
+     memset(capa->enabled_capa, 0, sizeof(capa->enabled_capa));
+     memset(capa->enabled_api, 0, sizeof(capa->enabled_api));
+     memset(sc->sc_fw_mcc, 0, sizeof(sc->sc_fw_mcc));
+
+     /*
+      * Parse firmware contents
+      */
+
+     uhdr = (const void *)fw->fw_fp->data;
+     if (*(const uint32_t *)fw->fw_fp->data != 0
+         || le32toh(uhdr->magic) != IWM_TLV_UCODE_MAGIC) {
+             device_printf(sc->sc_dev, "invalid firmware %s\n",
+                 sc->cfg->fw_name);
+             error = EINVAL;
+             goto out;
+     }
+
+     snprintf(sc->sc_fwver, sizeof(sc->sc_fwver), "%d.%d (API ver %d)",
+         IWM_UCODE_MAJOR(le32toh(uhdr->ver)),
+         IWM_UCODE_MINOR(le32toh(uhdr->ver)),
+         IWM_UCODE_API(le32toh(uhdr->ver)));
+     data = uhdr->data;
+     len = fw->fw_fp->datasize - sizeof(*uhdr);
+
+     while (len >= sizeof(tlv)) {
+             size_t tlv_len;
+             const void *tlv_data;
+
+             memcpy(&tlv, data, sizeof(tlv));
+             tlv_len = le32toh(tlv.length);
+             tlv_type = le32toh(tlv.type);
+
+             len -= sizeof(tlv);
+             data += sizeof(tlv);
+             tlv_data = data;
+
+             if (len < tlv_len) {
+                     device_printf(sc->sc_dev,
+                         "firmware too short: %zu bytes\n",
+                         len);
+                     error = EINVAL;
+                     goto parse_out;
+             }
+
+             switch ((int)tlv_type) {
+             case IWM_UCODE_TLV_PROBE_MAX_LEN:
+                     if (tlv_len < sizeof(uint32_t)) {
+                             device_printf(sc->sc_dev,
+                                 "%s: PROBE_MAX_LEN (%d) < 
sizeof(uint32_t)\n",
+                                 __func__,
+                                 (int) tlv_len);
+                             error = EINVAL;
+                             goto parse_out;
+                     }
+                     capa->max_probe_length =
+                         le32toh(*(const uint32_t *)tlv_data);
+                     /* limit it to something sensible */
+                     if (capa->max_probe_length >
+                         IWM_SCAN_OFFLOAD_PROBE_REQ_SIZE) {
+                             IWM_DPRINTF(sc, IWM_DEBUG_FIRMWARE_TLV,
+                                 "%s: IWM_UCODE_TLV_PROBE_MAX_LEN "
+                                 "ridiculous\n", __func__);
+                             error = EINVAL;
+                             goto parse_out;
+                     }
+                     break;
+             case IWM_UCODE_TLV_PAN:
+                     if (tlv_len) {
+                             device_printf(sc->sc_dev,
+                                 "%s: IWM_UCODE_TLV_PAN: tlv_len (%d) > 0\n",
+                                 __func__,
+                                 (int) tlv_len);
+                             error = EINVAL;
+                             goto parse_out;
+                     }
+                     capa->flags |= IWM_UCODE_TLV_FLAGS_PAN;
+                     break;
+             case IWM_UCODE_TLV_FLAGS:
+                     if (tlv_len < sizeof(uint32_t)) {
+                             device_printf(sc->sc_dev,
+                                 "%s: IWM_UCODE_TLV_FLAGS: tlv_len (%d) < 
sizeof(uint32_t)\n",
+                                 __func__,
+                                 (int) tlv_len);
+                             error = EINVAL;
+                             goto parse_out;
+                     }
+                     /*
+                      * Apparently there can be many flags, but Linux driver
+                      * parses only the first one, and so do we.
+                      *
+                      * XXX: why does this override IWM_UCODE_TLV_PAN?
+                      * Intentional or a bug?  Observations from
+                      * current firmware file:
+                      *  1) TLV_PAN is parsed first
+                      *  2) TLV_FLAGS contains TLV_FLAGS_PAN
+                      * ==> this resets TLV_PAN to itself... hnnnk
+                      */
+                     capa->flags = le32toh(*(const uint32_t *)tlv_data);
+                     break;
+             case IWM_UCODE_TLV_CSCHEME:
+                     if ((error = iwm_store_cscheme(sc,
+                         tlv_data, tlv_len)) != 0) {
+                             device_printf(sc->sc_dev,
+                                 "%s: iwm_store_cscheme(): returned %d\n",
+                                 __func__,
+                                 error);
+                             goto parse_out;
+                     }
+                     break;
+             case IWM_UCODE_TLV_NUM_OF_CPU:
+                     if (tlv_len != sizeof(uint32_t)) {
+                             device_printf(sc->sc_dev,
+                                 "%s: IWM_UCODE_TLV_NUM_OF_CPU: tlv_len (%d) 
!= sizeof(uint32_t)\n",
+                                 __func__,
+                                 (int) tlv_len);
+                             error = EINVAL;
+                             goto parse_out;
+                     }
+                     num_of_cpus = le32toh(*(const uint32_t *)tlv_data);
+                     if (num_of_cpus == 2) {
+                             fw->fw_sects[IWM_UCODE_REGULAR].is_dual_cpus =
+                                     TRUE;
+                             fw->fw_sects[IWM_UCODE_INIT].is_dual_cpus =
+                                     TRUE;
+                             fw->fw_sects[IWM_UCODE_WOWLAN].is_dual_cpus =
+                                     TRUE;
+                     } else if ((num_of_cpus > 2) || (num_of_cpus < 1)) {
+                             device_printf(sc->sc_dev,
+                                 "%s: Driver supports only 1 or 2 CPUs\n",
+                                 __func__);
+                             error = EINVAL;
+                             goto parse_out;
+                     }
+                     break;
+             case IWM_UCODE_TLV_SEC_RT:
+                     if ((error = iwm_firmware_store_section(sc,
+                         IWM_UCODE_REGULAR, tlv_data, tlv_len)) != 0) {
+                             device_printf(sc->sc_dev,
+                                 "%s: IWM_UCODE_REGULAR: 
iwm_firmware_store_section() failed; %d\n",
+                                 __func__,
+                                 error);
+                             goto parse_out;
+                     }
+                     break;
+             case IWM_UCODE_TLV_SEC_INIT:
+                     if ((error = iwm_firmware_store_section(sc,
+                         IWM_UCODE_INIT, tlv_data, tlv_len)) != 0) {
+                             device_printf(sc->sc_dev,
+                                 "%s: IWM_UCODE_INIT: 
iwm_firmware_store_section() failed; %d\n",
+                                 __func__,
+                                 error);
+                             goto parse_out;
+                     }
+                     break;
+             case IWM_UCODE_TLV_SEC_WOWLAN:
+                     if ((error = iwm_firmware_store_section(sc,
+                         IWM_UCODE_WOWLAN, tlv_data, tlv_len)) != 0) {
+                             device_printf(sc->sc_dev,
+                                 "%s: IWM_UCODE_WOWLAN: 
iwm_firmware_store_section() failed; %d\n",
+                                 __func__,
+                                 error);
+                             goto parse_out;
+                     }
+                     break;
+             case IWM_UCODE_TLV_DEF_CALIB:
+                     if (tlv_len != sizeof(struct iwm_tlv_calib_data)) {
+                             device_printf(sc->sc_dev,
+                                 "%s: IWM_UCODE_TLV_DEV_CALIB: tlv_len (%d) 
< sizeof(iwm_tlv_calib_data) (%d)\n",
+                                 __func__,
+                                 (int) tlv_len,
+                                 (int) sizeof(struct iwm_tlv_calib_data));
+                             error = EINVAL;
+                             goto parse_out;
+                     }
+                     if ((error = iwm_set_default_calib(sc, tlv_data)) != 0) 
{
+                             device_printf(sc->sc_dev,
+                                 "%s: iwm_set_default_calib() failed: %d\n",
+                                 __func__,
+                                 error);
+                             goto parse_out;
+                     }
+                     break;
+             case IWM_UCODE_TLV_PHY_SKU:
+                     if (tlv_len != sizeof(uint32_t)) {
+                             error = EINVAL;
+                             device_printf(sc->sc_dev,
+                                 "%s: IWM_UCODE_TLV_PHY_SKU: tlv_len (%d) < 
sizeof(uint32_t)\n",
+                                 __func__,
+                                 (int) tlv_len);
+                             goto parse_out;
+                     }
+                     sc->sc_fw.phy_config =
+                         le32toh(*(const uint32_t *)tlv_data);
+                     sc->sc_fw.valid_tx_ant = (sc->sc_fw.phy_config &
+                                               IWM_FW_PHY_CFG_TX_CHAIN) >>
+                                               IWM_FW_PHY_CFG_TX_CHAIN_POS;
+                     sc->sc_fw.valid_rx_ant = (sc->sc_fw.phy_config &
+                                               IWM_FW_PHY_CFG_RX_CHAIN) >>
+                                               IWM_FW_PHY_CFG_RX_CHAIN_POS;
+                     break;
+
+             case IWM_UCODE_TLV_API_CHANGES_SET: {
+                     if (tlv_len != sizeof(struct iwm_ucode_api)) {
+                             error = EINVAL;
+                             goto parse_out;
+                     }
+                     if (iwm_set_ucode_api_flags(sc, tlv_data, capa)) {
+                             error = EINVAL;
+                             goto parse_out;
+                     }
+                     break;
+             }
+
+             case IWM_UCODE_TLV_ENABLED_CAPABILITIES: {
+                     if (tlv_len != sizeof(struct iwm_ucode_capa)) {
+                             error = EINVAL;
+                             goto parse_out;
+                     }
+                     if (iwm_set_ucode_capabilities(sc, tlv_data, capa)) {
+                             error = EINVAL;
+                             goto parse_out;
+                     }
+                     break;
+             }
+
+             case 48: /* undocumented TLV */
+             case IWM_UCODE_TLV_SDIO_ADMA_ADDR:
+             case IWM_UCODE_TLV_FW_GSCAN_CAPA:
+                     /* ignore, not used by current driver */
+                     break;
+
+             case IWM_UCODE_TLV_SEC_RT_USNIFFER:
+                     if ((error = iwm_firmware_store_section(sc,
+                         IWM_UCODE_REGULAR_USNIFFER, tlv_data,
+                         tlv_len)) != 0)
+                             goto parse_out;
+                     break;
+
+             case IWM_UCODE_TLV_PAGING:
+                     if (tlv_len != sizeof(uint32_t)) {
+                             error = EINVAL;
+                             goto parse_out;
+                     }
+                     paging_mem_size = le32toh(*(const uint32_t *)tlv_data);
+
+                     IWM_DPRINTF(sc, IWM_DEBUG_FIRMWARE_TLV,
+                         "%s: Paging: paging enabled (size = %u bytes)\n",
+                         __func__, paging_mem_size);
+                     if (paging_mem_size > IWM_MAX_PAGING_IMAGE_SIZE) {
+                             device_printf(sc->sc_dev,
+                                     "%s: Paging: driver supports up to %u 
bytes for paging image\n",
+                                     __func__, IWM_MAX_PAGING_IMAGE_SIZE);
+                             error = EINVAL;
+                             goto out;
+                     }
+                     if (paging_mem_size & (IWM_FW_PAGING_SIZE - 1)) {
+                             device_printf(sc->sc_dev,
+                                 "%s: Paging: image isn't multiple %u\n",
+                                 __func__, IWM_FW_PAGING_SIZE);
+                             error = EINVAL;
+                             goto out;
+                     }
+
+                     sc->sc_fw.fw_sects[IWM_UCODE_REGULAR].paging_mem_size =
+                         paging_mem_size;
+                     usniffer_img = IWM_UCODE_REGULAR_USNIFFER;
+                     sc->sc_fw.fw_sects[usniffer_img].paging_mem_size =
+                         paging_mem_size;
+                     break;
+
+             case IWM_UCODE_TLV_N_SCAN_CHANNELS:
+                     if (tlv_len != sizeof(uint32_t)) {
+                             error = EINVAL;
+                             goto parse_out;
+                     }
+                     capa->n_scan_channels =
+                         le32toh(*(const uint32_t *)tlv_data);
+                     break;
+
+             case IWM_UCODE_TLV_FW_VERSION:
+                     if (tlv_len != sizeof(uint32_t) * 3) {
+                             error = EINVAL;
+                             goto parse_out;
+                     }
+                     snprintf(sc->sc_fwver, sizeof(sc->sc_fwver),
+                         "%d.%d.%d",
+                         le32toh(((const uint32_t *)tlv_data)[0]),
+                         le32toh(((const uint32_t *)tlv_data)[1]),
+                         le32toh(((const uint32_t *)tlv_data)[2]));
+                     break;
+
+             case IWM_UCODE_TLV_FW_MEM_SEG:
+                     break;
+
+             default:
+                     device_printf(sc->sc_dev,
+                         "%s: unknown firmware section %d, abort\n",
+                         __func__, tlv_type);
+                     error = EINVAL;
+                     goto parse_out;
+             }
+
+             len -= roundup(tlv_len, 4);
+             data += roundup(tlv_len, 4);
+     }
+
+     KASSERT(error == 0, ("unhandled error"));
+
+ parse_out:
+     if (error) {
+             device_printf(sc->sc_dev, "firmware parse error %d, "
+                 "section type %d\n", error, tlv_type);
+     }
+
+ out:
+     if (error) {
+             fw->fw_status = IWM_FW_STATUS_NONE;
+             if (fw->fw_fp != NULL)
+                     iwm_fw_info_free(fw);
+     } else
+             fw->fw_status = IWM_FW_STATUS_DONE;
+     wakeup(&sc->sc_fw);
+
+     return error;
+}
+
+/*
+ * DMA resource routines
+ */
+
+/* fwmem is used to load firmware onto the card */
+static int
+iwm_alloc_fwmem(struct iwm_softc *sc)
+{
+     /* Must be aligned on a 16-byte boundary. */
+     return iwm_dma_contig_alloc(sc->sc_dmat, &sc->fw_dma,
+         IWM_FH_MEM_TB_MAX_LENGTH, 16);
+}
+
+/* tx scheduler rings.  not used? */
+static int
+iwm_alloc_sched(struct iwm_softc *sc)
+{
+     /* TX scheduler rings must be aligned on a 1KB boundary. */
+     return iwm_dma_contig_alloc(sc->sc_dmat, &sc->sched_dma,
+         nitems(sc->txq) * sizeof(struct iwm_agn_scd_bc_tbl), 1024);
+}
+
+/* keep-warm page is used internally by the card.  see iwl-fh.h for more 
info */
+static int
+iwm_alloc_kw(struct iwm_softc *sc)
+{
+     return iwm_dma_contig_alloc(sc->sc_dmat, &sc->kw_dma, 4096, 4096);
+}
+
+/* interrupt cause table */
+static int
+iwm_alloc_ict(struct iwm_softc *sc)
+{
+     return iwm_dma_contig_alloc(sc->sc_dmat, &sc->ict_dma,
+         IWM_ICT_SIZE, 1<<IWM_ICT_PADDR_SHIFT);
+}
+
+static int
+iwm_alloc_rx_ring(struct iwm_softc *sc, struct iwm_rx_ring *ring)
+{
+     bus_size_t size;
+     int i, error;
+
+     ring->cur = 0;
+
+     /* Allocate RX descriptors (256-byte aligned). */
+     size = IWM_RX_RING_COUNT * sizeof(uint32_t);
+     error = iwm_dma_contig_alloc(sc->sc_dmat, &ring->desc_dma, size, 256);
+     if (error != 0) {
+             device_printf(sc->sc_dev,
+                 "could not allocate RX ring DMA memory\n");
+             goto fail;
+     }
+     ring->desc = ring->desc_dma.vaddr;
+
+     /* Allocate RX status area (16-byte aligned). */
+     error = iwm_dma_contig_alloc(sc->sc_dmat, &ring->stat_dma,
+         sizeof(*ring->stat), 16);
+     if (error != 0) {
+             device_printf(sc->sc_dev,
+                 "could not allocate RX status DMA memory\n");
+             goto fail;
+     }
+     ring->stat = ring->stat_dma.vaddr;
+
+        /* Create RX buffer DMA tag. */
+        error = bus_dma_tag_create(sc->sc_dmat, 1, 0,
+            BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
+            IWM_RBUF_SIZE, 1, IWM_RBUF_SIZE, 0, NULL, NULL, 
&ring->data_dmat);
+        if (error != 0) {
+                device_printf(sc->sc_dev,
+                    "%s: could not create RX buf DMA tag, error %d\n",
+                    __func__, error);
+                goto fail;
+        }
+
+     /* Allocate spare bus_dmamap_t for iwm_rx_addbuf() */
+     error = bus_dmamap_create(ring->data_dmat, 0, &ring->spare_map);
+     if (error != 0) {
+             device_printf(sc->sc_dev,
+                 "%s: could not create RX buf DMA map, error %d\n",
+                 __func__, error);
+             goto fail;
+     }
+     /*
+      * Allocate and map RX buffers.
+      */
+     for (i = 0; i < IWM_RX_RING_COUNT; i++) {
+             struct iwm_rx_data *data = &ring->data[i];
+             error = bus_dmamap_create(ring->data_dmat, 0, &data->map);
+             if (error != 0) {
+                     device_printf(sc->sc_dev,
+                         "%s: could not create RX buf DMA map, error %d\n",
+                         __func__, error);
+                     goto fail;
+             }
+             data->m = NULL;
+
+             if ((error = iwm_rx_addbuf(sc, IWM_RBUF_SIZE, i)) != 0) {
+                     goto fail;
+             }
+     }
+     return 0;
+
+fail:        iwm_free_rx_ring(sc, ring);
+     return error;
+}
+
+static void
+iwm_reset_rx_ring(struct iwm_softc *sc, struct iwm_rx_ring *ring)
+{
+     /* Reset the ring state */
+     ring->cur = 0;
+
+     /*
+      * The hw rx ring index in shared memory must also be cleared,
+      * otherwise the discrepancy can cause reprocessing chaos.
+      */
+     memset(sc->rxq.stat, 0, sizeof(*sc->rxq.stat));
+}
+
+static void
+iwm_free_rx_ring(struct iwm_softc *sc, struct iwm_rx_ring *ring)
+{
+     int i;
+
+     iwm_dma_contig_free(&ring->desc_dma);
+     iwm_dma_contig_free(&ring->stat_dma);
+
+     for (i = 0; i < IWM_RX_RING_COUNT; i++) {
+             struct iwm_rx_data *data = &ring->data[i];
+
+             if (data->m != NULL) {
+                     bus_dmamap_sync(ring->data_dmat, data->map,
+                         BUS_DMASYNC_POSTREAD);
+                     bus_dmamap_unload(ring->data_dmat, data->map);
+                     m_freem(data->m);
+                     data->m = NULL;
+             }
+             if (data->map != NULL) {
+                     bus_dmamap_destroy(ring->data_dmat, data->map);
+                     data->map = NULL;
+             }
+     }
+     if (ring->spare_map != NULL) {
+             bus_dmamap_destroy(ring->data_dmat, ring->spare_map);
+             ring->spare_map = NULL;
+     }
+     if (ring->data_dmat != NULL) {
+             bus_dma_tag_destroy(ring->data_dmat);
+             ring->data_dmat = NULL;
+     }
+}
+
+static int
+iwm_alloc_tx_ring(struct iwm_softc *sc, struct iwm_tx_ring *ring, int qid)
+{
+     bus_addr_t paddr;
+     bus_size_t size;
+     size_t maxsize;
+     int nsegments;
+     int i, error;
+
+     ring->qid = qid;
+     ring->queued = 0;
+     ring->cur = 0;
+
+     /* Allocate TX descriptors (256-byte aligned). */
+     size = IWM_TX_RING_COUNT * sizeof (struct iwm_tfd);
+     error = iwm_dma_contig_alloc(sc->sc_dmat, &ring->desc_dma, size, 256);
+     if (error != 0) {
+             device_printf(sc->sc_dev,
+                 "could not allocate TX ring DMA memory\n");
+             goto fail;
+     }
+     ring->desc = ring->desc_dma.vaddr;
+
+     /*
+      * We only use rings 0 through 9 (4 EDCA + cmd) so there is no need
+      * to allocate commands space for other rings.
+      */
+     if (qid > IWM_MVM_CMD_QUEUE)
+             return 0;
+
+     size = IWM_TX_RING_COUNT * sizeof(struct iwm_device_cmd);
+     error = iwm_dma_contig_alloc(sc->sc_dmat, &ring->cmd_dma, size, 4);
+     if (error != 0) {
+             device_printf(sc->sc_dev,
+                 "could not allocate TX cmd DMA memory\n");
+             goto fail;
+     }
+     ring->cmd = ring->cmd_dma.vaddr;
+
+     /* FW commands may require more mapped space than packets. */
+     if (qid == IWM_MVM_CMD_QUEUE) {
+             maxsize = IWM_RBUF_SIZE;
+             nsegments = 1;
+     } else {
+             maxsize = MCLBYTES;
+             nsegments = IWM_MAX_SCATTER - 2;
+     }
+
+     error = bus_dma_tag_create(sc->sc_dmat, 1, 0,
+         BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, maxsize,
+            nsegments, maxsize, 0, NULL, NULL, &ring->data_dmat);
+     if (error != 0) {
+             device_printf(sc->sc_dev, "could not create TX buf DMA tag\n");
+             goto fail;
+     }
+
+     paddr = ring->cmd_dma.paddr;
+     for (i = 0; i < IWM_TX_RING_COUNT; i++) {
+             struct iwm_tx_data *data = &ring->data[i];
+
+             data->cmd_paddr = paddr;
+             data->scratch_paddr = paddr + sizeof(struct iwm_cmd_header)
+                 + offsetof(struct iwm_tx_cmd, scratch);
+             paddr += sizeof(struct iwm_device_cmd);
+
+             error = bus_dmamap_create(ring->data_dmat, 0, &data->map);
+             if (error != 0) {
+                     device_printf(sc->sc_dev,
+                         "could not create TX buf DMA map\n");
+                     goto fail;
+             }
+     }
+     KASSERT(paddr == ring->cmd_dma.paddr + size,
+         ("invalid physical address"));
+     return 0;
+
+fail:        iwm_free_tx_ring(sc, ring);
+     return error;
+}
+
+static void
+iwm_reset_tx_ring(struct iwm_softc *sc, struct iwm_tx_ring *ring)
+{
+     int i;
+
+     for (i = 0; i < IWM_TX_RING_COUNT; i++) {
+             struct iwm_tx_data *data = &ring->data[i];
+
+             if (data->m != NULL) {
+                     bus_dmamap_sync(ring->data_dmat, data->map,
+                         BUS_DMASYNC_POSTWRITE);
+                     bus_dmamap_unload(ring->data_dmat, data->map);
+                     m_freem(data->m);
+                     data->m = NULL;
+             }
+     }
+     /* Clear TX descriptors. */
+     memset(ring->desc, 0, ring->desc_dma.size);
+     bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map,
+         BUS_DMASYNC_PREWRITE);
+     sc->qfullmsk &= ~(1 << ring->qid);
+     ring->queued = 0;
+     ring->cur = 0;
+
+     if (ring->qid == IWM_MVM_CMD_QUEUE && sc->cmd_hold_nic_awake)
+             iwm_pcie_clear_cmd_in_flight(sc);
+}
+
+static void
+iwm_free_tx_ring(struct iwm_softc *sc, struct iwm_tx_ring *ring)
+{
+     int i;
+
+     iwm_dma_contig_free(&ring->desc_dma);
+     iwm_dma_contig_free(&ring->cmd_dma);
+
+     for (i = 0; i < IWM_TX_RING_COUNT; i++) {
+             struct iwm_tx_data *data = &ring->data[i];
+
+             if (data->m != NULL) {
+                     bus_dmamap_sync(ring->data_dmat, data->map,
+                         BUS_DMASYNC_POSTWRITE);
+                     bus_dmamap_unload(ring->data_dmat, data->map);
+                     m_freem(data->m);
+                     data->m = NULL;
+             }
+             if (data->map != NULL) {
+                     bus_dmamap_destroy(ring->data_dmat, data->map);
+                     data->map = NULL;
+             }
+     }
+     if (ring->data_dmat != NULL) {
+             bus_dma_tag_destroy(ring->data_dmat);
+             ring->data_dmat = NULL;
+     }
+}
+
+/*
+ * High-level hardware frobbing routines
+ */
+
+static void
+iwm_enable_interrupts(struct iwm_softc *sc)
+{
+     sc->sc_intmask = IWM_CSR_INI_SET_MASK;
+     IWM_WRITE(sc, IWM_CSR_INT_MASK, sc->sc_intmask);
+}
+
+static void
+iwm_restore_interrupts(struct iwm_softc *sc)
+{
+     IWM_WRITE(sc, IWM_CSR_INT_MASK, sc->sc_intmask);
+}
+
+static void
+iwm_disable_interrupts(struct iwm_softc *sc)
+{
+     /* disable interrupts */
+     IWM_WRITE(sc, IWM_CSR_INT_MASK, 0);
+
+     /* acknowledge all interrupts */
+     IWM_WRITE(sc, IWM_CSR_INT, ~0);
+     IWM_WRITE(sc, IWM_CSR_FH_INT_STATUS, ~0);
+}
+
+static void
+iwm_ict_reset(struct iwm_softc *sc)
+{
+     iwm_disable_interrupts(sc);
+
+     /* Reset ICT table. */
+     memset(sc->ict_dma.vaddr, 0, IWM_ICT_SIZE);
+     sc->ict_cur = 0;
+
+     /* Set physical address of ICT table (4KB aligned). */
+     IWM_WRITE(sc, IWM_CSR_DRAM_INT_TBL_REG,
+         IWM_CSR_DRAM_INT_TBL_ENABLE
+         | IWM_CSR_DRAM_INIT_TBL_WRITE_POINTER
+         | IWM_CSR_DRAM_INIT_TBL_WRAP_CHECK
+         | sc->ict_dma.paddr >> IWM_ICT_PADDR_SHIFT);
+
+     /* Switch to ICT interrupt mode in driver. */
+     sc->sc_flags |= IWM_FLAG_USE_ICT;
+
+     /* Re-enable interrupts. */
+     IWM_WRITE(sc, IWM_CSR_INT, ~0);
+     iwm_enable_interrupts(sc);
+}
+
+/* iwlwifi pcie/trans.c */
+
+/*
+ * Since this .. hard-resets things, it's time to actually
+ * mark the first vap (if any) as having no mac context.
+ * It's annoying, but since the driver is potentially being
+ * stop/start'ed whilst active (thanks openbsd port!) we
+ * have to correctly track this.
+ */
+static void
+iwm_stop_device(struct iwm_softc *sc)
+{
+     struct ieee80211com *ic = &sc->sc_ic;
+     struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+     int chnl, qid;
+     uint32_t mask = 0;
+
+     /* tell the device to stop sending interrupts */
+     iwm_disable_interrupts(sc);
+
+     /*
+      * FreeBSD-local: mark the first vap as not-uploaded,
+      * so the next transition through auth/assoc
+      * will correctly populate the MAC context.
+      */
+     if (vap) {
+             struct iwm_vap *iv = IWM_VAP(vap);
+             iv->phy_ctxt = NULL;
+             iv->is_uploaded = 0;
+     }
+
+     /* device going down, Stop using ICT table */
+     sc->sc_flags &= ~IWM_FLAG_USE_ICT;
+
+     /* stop tx and rx.  tx and rx bits, as usual, are from if_iwn */
+
+     if (iwm_nic_lock(sc)) {
+             iwm_write_prph(sc, IWM_SCD_TXFACT, 0);
+
+             /* Stop each Tx DMA channel */
+             for (chnl = 0; chnl < IWM_FH_TCSR_CHNL_NUM; chnl++) {
+                     IWM_WRITE(sc,
+                         IWM_FH_TCSR_CHNL_TX_CONFIG_REG(chnl), 0);
+                     mask |= IWM_FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(chnl);
+             }
+
+             /* Wait for DMA channels to be idle */
+             if (!iwm_poll_bit(sc, IWM_FH_TSSR_TX_STATUS_REG, mask, mask,
+                 5000)) {
+                     device_printf(sc->sc_dev,
+                         "Failing on timeout while stopping DMA channel: 
[0x%08x]\n",
+                         IWM_READ(sc, IWM_FH_TSSR_TX_STATUS_REG));
+             }
+             iwm_nic_unlock(sc);
+     }
+     iwm_pcie_rx_stop(sc);
+
+     /* Stop RX ring. */
+     iwm_reset_rx_ring(sc, &sc->rxq);
+
+     /* Reset all TX rings. */
+     for (qid = 0; qid < nitems(sc->txq); qid++)
+             iwm_reset_tx_ring(sc, &sc->txq[qid]);
+
+     if (sc->cfg->device_family == IWM_DEVICE_FAMILY_7000) {
+             /* Power-down device's busmaster DMA clocks */
+             if (iwm_nic_lock(sc)) {
+                     iwm_write_prph(sc, IWM_APMG_CLK_DIS_REG,
+                         IWM_APMG_CLK_VAL_DMA_CLK_RQT);
+                     iwm_nic_unlock(sc);
+             }
+             DELAY(5);
+     }
+
+     /* Make sure (redundant) we've released our request to stay awake */
+     IWM_CLRBITS(sc, IWM_CSR_GP_CNTRL,
+         IWM_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
+
+     /* Stop the device, and put it in low power state */
+     iwm_apm_stop(sc);
+
+     /* Upon stop, the APM issues an interrupt if HW RF kill is set.
+      * Clean again the interrupt here
+      */
+     iwm_disable_interrupts(sc);
+     /* stop and reset the on-board processor */
+     IWM_WRITE(sc, IWM_CSR_RESET, IWM_CSR_RESET_REG_FLAG_SW_RESET);
+
+     /*
+      * Even if we stop the HW, we still want the RF kill
+      * interrupt
+      */
+     iwm_enable_rfkill_int(sc);
+     iwm_check_rfkill(sc);
+}
+
+/* iwlwifi: mvm/ops.c */
+static void
+iwm_mvm_nic_config(struct iwm_softc *sc)
+{
+     uint8_t radio_cfg_type, radio_cfg_step, radio_cfg_dash;
+     uint32_t reg_val = 0;
+     uint32_t phy_config = iwm_mvm_get_phy_config(sc);
+
+     radio_cfg_type = (phy_config & IWM_FW_PHY_CFG_RADIO_TYPE) >>
+         IWM_FW_PHY_CFG_RADIO_TYPE_POS;
+     radio_cfg_step = (phy_config & IWM_FW_PHY_CFG_RADIO_STEP) >>
+         IWM_FW_PHY_CFG_RADIO_STEP_POS;
+     radio_cfg_dash = (phy_config & IWM_FW_PHY_CFG_RADIO_DASH) >>
+         IWM_FW_PHY_CFG_RADIO_DASH_POS;
+
+     /* SKU control */
+     reg_val |= IWM_CSR_HW_REV_STEP(sc->sc_hw_rev) <<
+         IWM_CSR_HW_IF_CONFIG_REG_POS_MAC_STEP;
+     reg_val |= IWM_CSR_HW_REV_DASH(sc->sc_hw_rev) <<
+         IWM_CSR_HW_IF_CONFIG_REG_POS_MAC_DASH;
+
+     /* radio configuration */
+     reg_val |= radio_cfg_type << IWM_CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE;
+     reg_val |= radio_cfg_step << IWM_CSR_HW_IF_CONFIG_REG_POS_PHY_STEP;
+     reg_val |= radio_cfg_dash << IWM_CSR_HW_IF_CONFIG_REG_POS_PHY_DASH;
+
+     IWM_WRITE(sc, IWM_CSR_HW_IF_CONFIG_REG, reg_val);
+
+     IWM_DPRINTF(sc, IWM_DEBUG_RESET,
+         "Radio type=0x%x-0x%x-0x%x\n", radio_cfg_type,
+         radio_cfg_step, radio_cfg_dash);
+
+     /*
+      * W/A : NIC is stuck in a reset state after Early PCIe power off
+      * (PCIe power is lost before PERST# is asserted), causing ME FW
+      * to lose ownership and not being able to obtain it back.
+      */
+     if (sc->cfg->device_family == IWM_DEVICE_FAMILY_7000) {
+             iwm_set_bits_mask_prph(sc, IWM_APMG_PS_CTRL_REG,
+                 IWM_APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS,
+                 ~IWM_APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS);
+     }
+}
+
+static int
+iwm_nic_rx_init(struct iwm_softc *sc)
+{
+     /*
+      * Initialize RX ring.  This is from the iwn driver.
+      */
+     memset(sc->rxq.stat, 0, sizeof(*sc->rxq.stat));
+
+     /* Stop Rx DMA */
+     iwm_pcie_rx_stop(sc);
+
+     if (!iwm_nic_lock(sc))
+             return EBUSY;
+
+     /* reset and flush pointers */
+     IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_RBDCB_WPTR, 0);
+     IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_FLUSH_RB_REQ, 0);
+     IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_RDPTR, 0);
+     IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_RBDCB_WPTR_REG, 0);
+
+     /* Set physical address of RX ring (256-byte aligned). */
+     IWM_WRITE(sc,
+         IWM_FH_RSCSR_CHNL0_RBDCB_BASE_REG, sc->rxq.desc_dma.paddr >> 8);
+
+     /* Set physical address of RX status (16-byte aligned). */
+     IWM_WRITE(sc,
+         IWM_FH_RSCSR_CHNL0_STTS_WPTR_REG, sc->rxq.stat_dma.paddr >> 4);
+
+     /* Enable Rx DMA
+      * XXX 5000 HW isn't supported by the iwm(4) driver.
+      * IWM_FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY is set because of HW bug in
+      *      the credit mechanism in 5000 HW RX FIFO
+      * Direct rx interrupts to hosts
+      * Rx buffer size 4 or 8k or 12k
+      * RB timeout 0x10
+      * 256 RBDs
+      */
+     IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_CONFIG_REG,
+         IWM_FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL            |
+         IWM_FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY               |  /* HW bug */
+#ifdef __HAIKU__ /* We don't support mbuf refcounts, are required for 
multiframe */
+             IWM_FH_RCSR_CHNL0_RX_CONFIG_SINGLE_FRAME_MSK |
+#endif
+         IWM_FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL   |
+         IWM_FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K            |
+         (IWM_RX_RB_TIMEOUT << IWM_FH_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS) |
+         IWM_RX_QUEUE_SIZE_LOG << IWM_FH_RCSR_RX_CONFIG_RBDCB_SIZE_POS);
+
+     IWM_WRITE_1(sc, IWM_CSR_INT_COALESCING, IWM_HOST_INT_TIMEOUT_DEF);
+
+     /* W/A for interrupt coalescing bug in 7260 and 3160 */
+     if (sc->cfg->host_interrupt_operation_mode)
+             IWM_SETBITS(sc, IWM_CSR_INT_COALESCING, IWM_HOST_INT_OPER_MODE);
+
+     /*
+      * Thus sayeth el jefe (iwlwifi) via a comment:
+      *
+      * This value should initially be 0 (before preparing any
+      * RBs), should be 8 after preparing the first 8 RBs (for example)
+      */
+     IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_WPTR, 8);
+
+     iwm_nic_unlock(sc);
+
+     return 0;
+}
+
+static int
+iwm_nic_tx_init(struct iwm_softc *sc)
+{
+     int qid;
+
+     if (!iwm_nic_lock(sc))
+             return EBUSY;
+
+     /* Deactivate TX scheduler. */
+     iwm_write_prph(sc, IWM_SCD_TXFACT, 0);
+
+     /* Set physical address of "keep warm" page (16-byte aligned). */
+     IWM_WRITE(sc, IWM_FH_KW_MEM_ADDR_REG, sc->kw_dma.paddr >> 4);
+
+     /* Initialize TX rings. */
+     for (qid = 0; qid < nitems(sc->txq); qid++) {
+             struct iwm_tx_ring *txq = &sc->txq[qid];
+
+             /* Set physical address of TX ring (256-byte aligned). */
+             IWM_WRITE(sc, IWM_FH_MEM_CBBC_QUEUE(qid),
+                 txq->desc_dma.paddr >> 8);
+             IWM_DPRINTF(sc, IWM_DEBUG_XMIT,
+                 "%s: loading ring %d descriptors (%p) at %lx\n",
+                 __func__,
+                 qid, txq->desc,
+                 (unsigned long) (txq->desc_dma.paddr >> 8));
+     }
+
+     iwm_write_prph(sc, IWM_SCD_GP_CTRL, IWM_SCD_GP_CTRL_AUTO_ACTIVE_MODE);
+
+     iwm_nic_unlock(sc);
+
+     return 0;
+}
+
+static int
+iwm_nic_init(struct iwm_softc *sc)
+{
+     int error;
+
+     iwm_apm_init(sc);
+     if (sc->cfg->device_family == IWM_DEVICE_FAMILY_7000)
+             iwm_set_pwr(sc);
+
+     iwm_mvm_nic_config(sc);
+
+     if ((error = iwm_nic_rx_init(sc)) != 0)
+             return error;
+
+     /*
+      * Ditto for TX, from iwn
+      */
+     if ((error = iwm_nic_tx_init(sc)) != 0)
+             return error;
+
+     IWM_DPRINTF(sc, IWM_DEBUG_RESET,
+         "%s: shadow registers enabled\n", __func__);
+     IWM_SETBITS(sc, IWM_CSR_MAC_SHADOW_REG_CTRL, 0x800fffff);
+
+     return 0;
+}
+
+int
+iwm_enable_txq(struct iwm_softc *sc, int sta_id, int qid, int fifo)
+{
+     if (!iwm_nic_lock(sc)) {
+             device_printf(sc->sc_dev,
+                 "%s: cannot enable txq %d\n",
+                 __func__,
+                 qid);
+             return EBUSY;
+     }
+
+     IWM_WRITE(sc, IWM_HBUS_TARG_WRPTR, qid << 8 | 0);
+
+     if (qid == IWM_MVM_CMD_QUEUE) {
+             /* unactivate before configuration */
+             iwm_write_prph(sc, IWM_SCD_QUEUE_STATUS_BITS(qid),
+                 (0 << IWM_SCD_QUEUE_STTS_REG_POS_ACTIVE)
+                 | (1 << IWM_SCD_QUEUE_STTS_REG_POS_SCD_ACT_EN));
+
+             iwm_nic_unlock(sc);
+
+             iwm_clear_bits_prph(sc, IWM_SCD_AGGR_SEL, (1 << qid));
+
+             if (!iwm_nic_lock(sc)) {
+                     device_printf(sc->sc_dev,
+                         "%s: cannot enable txq %d\n", __func__, qid);
+                     return EBUSY;
+             }
+             iwm_write_prph(sc, IWM_SCD_QUEUE_RDPTR(qid), 0);
+             iwm_nic_unlock(sc);
+
+             iwm_write_mem32(sc, sc->scd_base_addr + 
IWM_SCD_CONTEXT_QUEUE_OFFSET(qid), 0);
+             /* Set scheduler window size and frame limit. */
+             iwm_write_mem32(sc,
+                 sc->scd_base_addr + IWM_SCD_CONTEXT_QUEUE_OFFSET(qid) +
+                 sizeof(uint32_t),
+                 ((IWM_FRAME_LIMIT << IWM_SCD_QUEUE_CTX_REG2_WIN_SIZE_POS) &
+                 IWM_SCD_QUEUE_CTX_REG2_WIN_SIZE_MSK) |
+                 ((IWM_FRAME_LIMIT << 
IWM_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_POS) &
+                 IWM_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_MSK));
+
+             if (!iwm_nic_lock(sc)) {
+                     device_printf(sc->sc_dev,
+                         "%s: cannot enable txq %d\n", __func__, qid);
+                     return EBUSY;
+             }
+             iwm_write_prph(sc, IWM_SCD_QUEUE_STATUS_BITS(qid),
+                 (1 << IWM_SCD_QUEUE_STTS_REG_POS_ACTIVE) |
+                 (fifo << IWM_SCD_QUEUE_STTS_REG_POS_TXF) |
+                 (1 << IWM_SCD_QUEUE_STTS_REG_POS_WSL) |
+                 IWM_SCD_QUEUE_STTS_REG_MSK);
+     } else {
+             struct iwm_scd_txq_cfg_cmd cmd;
+             int error;
+
+             iwm_nic_unlock(sc);
+
+             memset(&cmd, 0, sizeof(cmd));
+             cmd.scd_queue = qid;
+             cmd.enable = 1;
+             cmd.sta_id = sta_id;
+             cmd.tx_fifo = fifo;
+             cmd.aggregate = 0;
+             cmd.window = IWM_FRAME_LIMIT;
+
+             error = iwm_mvm_send_cmd_pdu(sc, IWM_SCD_QUEUE_CFG, 
IWM_CMD_SYNC,
+                 sizeof(cmd), &cmd);
+             if (error) {
+                     device_printf(sc->sc_dev,
+                         "cannot enable txq %d\n", qid);
+                     return error;
+             }
+
+             if (!iwm_nic_lock(sc))
+                     return EBUSY;
+     }
+
+     iwm_write_prph(sc, IWM_SCD_EN_CTRL,
+         iwm_read_prph(sc, IWM_SCD_EN_CTRL) | qid);
+
+     iwm_nic_unlock(sc);
+
+     IWM_DPRINTF(sc, IWM_DEBUG_XMIT, "%s: enabled txq %d FIFO %d\n",
+         __func__, qid, fifo);
+
+     return 0;
+}
+
+static int
+iwm_trans_pcie_fw_alive(struct iwm_softc *sc, uint32_t scd_base_addr)
+{
+     int error, chnl;
+
+     int clear_dwords = (IWM_SCD_TRANS_TBL_MEM_UPPER_BOUND -
+         IWM_SCD_CONTEXT_MEM_LOWER_BOUND) / sizeof(uint32_t);
+
+     if (!iwm_nic_lock(sc))
+             return EBUSY;
+
+#ifndef __HAIKU__
+     iwm_ict_reset(sc);
+#endif
+
+     sc->scd_base_addr = iwm_read_prph(sc, IWM_SCD_SRAM_BASE_ADDR);
+     if (scd_base_addr != 0 &&
+         scd_base_addr != sc->scd_base_addr) {
+             device_printf(sc->sc_dev,
+                 "%s: sched addr mismatch: alive: 0x%x prph: 0x%x\n",
+                 __func__, sc->scd_base_addr, scd_base_addr);
+     }
+
+     iwm_nic_unlock(sc);
+
+     /* reset context data, TX status and translation data */
+     error = iwm_write_mem(sc,
+         sc->scd_base_addr + IWM_SCD_CONTEXT_MEM_LOWER_BOUND,
+         NULL, clear_dwords);
+     if (error)
+             return EBUSY;
+
+     if (!iwm_nic_lock(sc))
+             return EBUSY;
+
+     /* Set physical address of TX scheduler rings (1KB aligned). */
+     iwm_write_prph(sc, IWM_SCD_DRAM_BASE_ADDR, sc->sched_dma.paddr >> 10);
+
+     iwm_write_prph(sc, IWM_SCD_CHAINEXT_EN, 0);
+
+     iwm_nic_unlock(sc);
+
+     /* enable command channel */
+     error = iwm_enable_txq(sc, 0 /* unused */, IWM_MVM_CMD_QUEUE, 7);
+     if (error)
+             return error;
+
+     if (!iwm_nic_lock(sc))
+             return EBUSY;
+
+     iwm_write_prph(sc, IWM_SCD_TXFACT, 0xff);
+
+     /* Enable DMA channels. */
+     for (chnl = 0; chnl < IWM_FH_TCSR_CHNL_NUM; chnl++) {
+             IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_CONFIG_REG(chnl),
+                 IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE |
+                 IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE);
+     }
+
+     IWM_SETBITS(sc, IWM_FH_TX_CHICKEN_BITS_REG,
+         IWM_FH_TX_CHICKEN_BITS_SCD_AUTO_RETRY_EN);
+
+     iwm_nic_unlock(sc);
+
+     /* Enable L1-Active */
+     if (sc->cfg->device_family != IWM_DEVICE_FAMILY_8000) {
+             iwm_clear_bits_prph(sc, IWM_APMG_PCIDEV_STT_REG,
+                 IWM_APMG_PCIDEV_STT_VAL_L1_ACT_DIS);
+     }
+
+     return error;
+}
+
+/*
+ * NVM read access and content parsing.  We do not support
+ * external NVM or writing NVM.
+ * iwlwifi/mvm/nvm.c
+ */
+
+/* Default NVM size to read */
+#define IWM_NVM_DEFAULT_CHUNK_SIZE   (2*1024)
+
+#define IWM_NVM_WRITE_OPCODE 1
+#define IWM_NVM_READ_OPCODE 0
+
+/* load nvm chunk response */
+enum {
+     IWM_READ_NVM_CHUNK_SUCCEED = 0,
+     IWM_READ_NVM_CHUNK_NOT_VALID_ADDRESS = 1
+};
+
+static int
+iwm_nvm_read_chunk(struct iwm_softc *sc, uint16_t section,
+     uint16_t offset, uint16_t length, uint8_t *data, uint16_t *len)
+{
+     struct iwm_nvm_access_cmd nvm_access_cmd = {
+             .offset = htole16(offset),
+             .length = htole16(length),
+             .type = htole16(section),
+             .op_code = IWM_NVM_READ_OPCODE,
+     };
+     struct iwm_nvm_access_resp *nvm_resp;
+     struct iwm_rx_packet *pkt;
+     struct iwm_host_cmd cmd = {
+             .id = IWM_NVM_ACCESS_CMD,
+             .flags = IWM_CMD_WANT_SKB | IWM_CMD_SEND_IN_RFKILL,
+             .data = { &nvm_access_cmd, },
+     };
+     int ret, bytes_read, offset_read;
+     uint8_t *resp_data;
+
+     cmd.len[0] = sizeof(struct iwm_nvm_access_cmd);
+
+     ret = iwm_send_cmd(sc, &cmd);
+     if (ret) {
+             device_printf(sc->sc_dev,
+                 "Could not send NVM_ACCESS command (error=%d)\n", ret);
+             return ret;
+     }
+
+     pkt = cmd.resp_pkt;
+
+     /* Extract NVM response */
+     nvm_resp = (void *)pkt->data;
+     ret = le16toh(nvm_resp->status);
+     bytes_read = le16toh(nvm_resp->length);
+     offset_read = le16toh(nvm_resp->offset);
+     resp_data = nvm_resp->data;
+     if (ret) {
+             if ((offset != 0) &&
+                 (ret == IWM_READ_NVM_CHUNK_NOT_VALID_ADDRESS)) {
+                     /*
+                      * meaning of NOT_VALID_ADDRESS:
+                      * driver try to read chunk from address that is
+                      * multiple of 2K and got an error since addr is empty.
+                      * meaning of (offset != 0): driver already
+                      * read valid data from another chunk so this case
+                      * is not an error.
+                      */
+                     IWM_DPRINTF(sc, IWM_DEBUG_EEPROM | IWM_DEBUG_RESET,
+                                 "NVM access command failed on offset 0x%x 
since that section size is multiple 2K\n",
+                                 offset);
+                     *len = 0;
+                     ret = 0;
+             } else {
+                     IWM_DPRINTF(sc, IWM_DEBUG_EEPROM | IWM_DEBUG_RESET,
+                                 "NVM access command failed with status 
%d\n", ret);
+                     ret = EIO;
+             }
+             goto exit;
+     }
+
+     if (offset_read != offset) {
+             device_printf(sc->sc_dev,
+                 "NVM ACCESS response with invalid offset %d\n",
+                 offset_read);
+             ret = EINVAL;
+             goto exit;
+     }
+
+     if (bytes_read > length) {
+             device_printf(sc->sc_dev,
+                 "NVM ACCESS response with too much data "
+                 "(%d bytes requested, %d bytes received)\n",
+                 length, bytes_read);
+             ret = EINVAL;
+             goto exit;
+     }
+
+     /* Write data to NVM */
+     memcpy(data + offset, resp_data, bytes_read);
+     *len = bytes_read;
+
+ exit:
+     iwm_free_resp(sc, &cmd);
+     return ret;
+}
+
+/*
+ * Reads an NVM section completely.
+ * NICs prior to 7000 family don't have a real NVM, but just read
+ * section 0 which is the EEPROM. Because the EEPROM reading is unlimited
+ * by uCode, we need to manually check in this case that we don't
+ * overflow and try to read more than the EEPROM size.
+ * For 7000 family NICs, we supply the maximal size we can read, and
+ * the uCode fills the response with as much data as we can,
+ * without overflowing, so no check is needed.
+ */
+static int
+iwm_nvm_read_section(struct iwm_softc *sc,
+     uint16_t section, uint8_t *data, uint16_t *len, uint32_t size_read)
+{
+     uint16_t seglen, length, offset = 0;
+     int ret;
+
+     /* Set nvm section read length */
+     length = IWM_NVM_DEFAULT_CHUNK_SIZE;
+
+     seglen = length;
+
+     /* Read the NVM until exhausted (reading less than requested) */
+     while (seglen == length) {
+             /* Check no memory assumptions fail and cause an overflow */
+             if ((size_read + offset + length) >
+                 sc->cfg->eeprom_size) {
+                     device_printf(sc->sc_dev,
+                         "EEPROM size is too small for NVM\n");
+                     return ENOBUFS;
+             }
+
+             ret = iwm_nvm_read_chunk(sc, section, offset, length, data, 
&seglen);
+             if (ret) {
+                     IWM_DPRINTF(sc, IWM_DEBUG_EEPROM | IWM_DEBUG_RESET,
+                                 "Cannot read NVM from section %d offset %d, 
length %d\n",
+                                 section, offset, length);
+                     return ret;
+             }
+             offset += seglen;
+     }
+
+     IWM_DPRINTF(sc, IWM_DEBUG_EEPROM | IWM_DEBUG_RESET,
+                 "NVM section %d read completed\n", section);
+     *len = offset;
+     return 0;
+}
+
+/*
+ * BEGIN IWM_NVM_PARSE
+ */
+
+/* iwlwifi/iwl-nvm-parse.c */
+
+/* NVM offsets (in words) definitions */
+enum iwm_nvm_offsets {
+     /* NVM HW-Section offset (in words) definitions */
+     IWM_HW_ADDR = 0x15,
+
+/* NVM SW-Section offset (in words) definitions */
+     IWM_NVM_SW_SECTION = 0x1C0,
+     IWM_NVM_VERSION = 0,
+     IWM_RADIO_CFG = 1,
+     IWM_SKU = 2,
+     IWM_N_HW_ADDRS = 3,
+     IWM_NVM_CHANNELS = 0x1E0 - IWM_NVM_SW_SECTION,
+
+/* NVM calibration section offset (in words) definitions */
+     IWM_NVM_CALIB_SECTION = 0x2B8,
+     IWM_XTAL_CALIB = 0x316 - IWM_NVM_CALIB_SECTION
+};
+
+enum iwm_8000_nvm_offsets {
+     /* NVM HW-Section offset (in words) definitions */
+     IWM_HW_ADDR0_WFPM_8000 = 0x12,
+     IWM_HW_ADDR1_WFPM_8000 = 0x16,
+     IWM_HW_ADDR0_PCIE_8000 = 0x8A,
+     IWM_HW_ADDR1_PCIE_8000 = 0x8E,
+     IWM_MAC_ADDRESS_OVERRIDE_8000 = 1,
+
+     /* NVM SW-Section offset (in words) definitions */
+     IWM_NVM_SW_SECTION_8000 = 0x1C0,
+     IWM_NVM_VERSION_8000 = 0,
+     IWM_RADIO_CFG_8000 = 0,
+     IWM_SKU_8000 = 2,
+     IWM_N_HW_ADDRS_8000 = 3,
+
+     /* NVM REGULATORY -Section offset (in words) definitions */
+     IWM_NVM_CHANNELS_8000 = 0,
+     IWM_NVM_LAR_OFFSET_8000_OLD = 0x4C7,
+     IWM_NVM_LAR_OFFSET_8000 = 0x507,
+     IWM_NVM_LAR_ENABLED_8000 = 0x7,
+
+     /* NVM calibration section offset (in words) definitions */
+     IWM_NVM_CALIB_SECTION_8000 = 0x2B8,
+     IWM_XTAL_CALIB_8000 = 0x316 - IWM_NVM_CALIB_SECTION_8000
+};
+
+/* SKU Capabilities (actual values from NVM definition) */
+enum nvm_sku_bits {
+     IWM_NVM_SKU_CAP_BAND_24GHZ      = (1 << 0),
+     IWM_NVM_SKU_CAP_BAND_52GHZ      = (1 << 1),
+     IWM_NVM_SKU_CAP_11N_ENABLE      = (1 << 2),
+     IWM_NVM_SKU_CAP_11AC_ENABLE     = (1 << 3),
+};
+
+/* radio config bits (actual values from NVM definition) */
+#define IWM_NVM_RF_CFG_DASH_MSK(x)   (x & 0x3)         /* bits 0-1   */
+#define IWM_NVM_RF_CFG_STEP_MSK(x)   ((x >> 2)  & 0x3) /* bits 2-3   */
+#define IWM_NVM_RF_CFG_TYPE_MSK(x)   ((x >> 4)  & 0x3) /* bits 4-5   */
+#define IWM_NVM_RF_CFG_PNUM_MSK(x)   ((x >> 6)  & 0x3) /* bits 6-7   */
+#define IWM_NVM_RF_CFG_TX_ANT_MSK(x) ((x >> 8)  & 0xF) /* bits 8-11  */
+#define IWM_NVM_RF_CFG_RX_ANT_MSK(x) ((x >> 12) & 0xF) /* bits 12-15 */
+
+#define IWM_NVM_RF_CFG_FLAVOR_MSK_8000(x)    (x & 0xF)
+#define IWM_NVM_RF_CFG_DASH_MSK_8000(x)              ((x >> 4) & 0xF)
+#define IWM_NVM_RF_CFG_STEP_MSK_8000(x)              ((x >> 8) & 0xF)
+#define IWM_NVM_RF_CFG_TYPE_MSK_8000(x)              ((x >> 12) & 0xFFF)
+#define IWM_NVM_RF_CFG_TX_ANT_MSK_8000(x)    ((x >> 24) & 0xF)
+#define IWM_NVM_RF_CFG_RX_ANT_MSK_8000(x)    ((x >> 28) & 0xF)
+
+#define DEFAULT_MAX_TX_POWER 16
+
+/**
+ * enum iwm_nvm_channel_flags - channel flags in NVM
+ * @IWM_NVM_CHANNEL_VALID: channel is usable for this SKU/geo
+ * @IWM_NVM_CHANNEL_IBSS: usable as an IBSS channel
+ * @IWM_NVM_CHANNEL_ACTIVE: active scanning allowed
+ * @IWM_NVM_CHANNEL_RADAR: radar detection required
+ * XXX cannot find this (DFS) flag in iwm-nvm-parse.c
+ * @IWM_NVM_CHANNEL_DFS: dynamic freq selection candidate
+ * @IWM_NVM_CHANNEL_WIDE: 20 MHz channel okay (?)
+ * @IWM_NVM_CHANNEL_40MHZ: 40 MHz channel okay (?)
+ * @IWM_NVM_CHANNEL_80MHZ: 80 MHz channel okay (?)
+ * @IWM_NVM_CHANNEL_160MHZ: 160 MHz channel okay (?)
+ */
+enum iwm_nvm_channel_flags {
+     IWM_NVM_CHANNEL_VALID = (1 << 0),
+     IWM_NVM_CHANNEL_IBSS = (1 << 1),
+     IWM_NVM_CHANNEL_ACTIVE = (1 << 3),
+     IWM_NVM_CHANNEL_RADAR = (1 << 4),
+     IWM_NVM_CHANNEL_DFS = (1 << 7),
+     IWM_NVM_CHANNEL_WIDE = (1 << 8),
+     IWM_NVM_CHANNEL_40MHZ = (1 << 9),
+     IWM_NVM_CHANNEL_80MHZ = (1 << 10),
+     IWM_NVM_CHANNEL_160MHZ = (1 << 11),
+};
+
+/*
+ * Translate EEPROM flags to net80211.
+ */
+static uint32_t
+iwm_eeprom_channel_flags(uint16_t ch_flags)
+{
+     uint32_t nflags;
+
+     nflags = 0;
+     if ((ch_flags & IWM_NVM_CHANNEL_ACTIVE) == 0)
+             nflags |= IEEE80211_CHAN_PASSIVE;
+     if ((ch_flags & IWM_NVM_CHANNEL_IBSS) == 0)
+             nflags |= IEEE80211_CHAN_NOADHOC;
+     if (ch_flags & IWM_NVM_CHANNEL_RADAR) {
+             nflags |= IEEE80211_CHAN_DFS;
+             /* Just in case. */
+             nflags |= IEEE80211_CHAN_NOADHOC;
+     }
+
+     return (nflags);
+}
+
+static void
+iwm_add_channel_band(struct iwm_softc *sc, struct ieee80211_channel chans[],
+    int maxchans, int *nchans, int ch_idx, size_t ch_num,
+    const uint8_t bands[])
+{
+     const uint16_t * const nvm_ch_flags = sc->nvm_data->nvm_ch_flags;
+     uint32_t nflags;
+     uint16_t ch_flags;
+     uint8_t ieee;
+     int error;
+
+     for (; ch_idx < ch_num; ch_idx++) {
+             ch_flags = le16_to_cpup(nvm_ch_flags + ch_idx);
+             if (sc->cfg->device_family == IWM_DEVICE_FAMILY_7000)
+                     ieee = iwm_nvm_channels[ch_idx];
+             else
+                     ieee = iwm_nvm_channels_8000[ch_idx];
+
+             if (!(ch_flags & IWM_NVM_CHANNEL_VALID)) {
+                     IWM_DPRINTF(sc, IWM_DEBUG_EEPROM,
+                         "Ch. %d Flags %x [%sGHz] - No traffic\n",
+                         ieee, ch_flags,
+                         (ch_idx >= IWM_NUM_2GHZ_CHANNELS) ?
+                         "5.2" : "2.4");
+                     continue;
+             }
+
+             nflags = iwm_eeprom_channel_flags(ch_flags);
+             error = ieee80211_add_channel(chans, maxchans, nchans,
+                 ieee, 0, 0, nflags, bands);
+             if (error != 0)
+                     break;
+
+             IWM_DPRINTF(sc, IWM_DEBUG_EEPROM,
+                 "Ch. %d Flags %x [%sGHz] - Added\n",
+                 ieee, ch_flags,
+                 (ch_idx >= IWM_NUM_2GHZ_CHANNELS) ?
+                 "5.2" : "2.4");
+     }
+}
+
+static void
+iwm_init_channel_map(struct ieee80211com *ic, int maxchans, int *nchans,
+    struct ieee80211_channel chans[])
+{
+     struct iwm_softc *sc = ic->ic_softc;
+     struct iwm_nvm_data *data = sc->nvm_data;
+     uint8_t bands[IEEE80211_MODE_BYTES];
+     size_t ch_num;
+
+     memset(bands, 0, sizeof(bands));
+     /* 1-13: 11b/g channels. */
+     setbit(bands, IEEE80211_MODE_11B);
+     setbit(bands, IEEE80211_MODE_11G);
+     iwm_add_channel_band(sc, chans, maxchans, nchans, 0,
+         IWM_NUM_2GHZ_CHANNELS - 1, bands);
+
+     /* 14: 11b channel only. */
+     clrbit(bands, IEEE80211_MODE_11G);
+     iwm_add_channel_band(sc, chans, maxchans, nchans,
+         IWM_NUM_2GHZ_CHANNELS - 1, IWM_NUM_2GHZ_CHANNELS, bands);
+
+     if (data->sku_cap_band_52GHz_enable) {
+             if (sc->cfg->device_family == IWM_DEVICE_FAMILY_7000)
+                     ch_num = nitems(iwm_nvm_channels);
+             else
+                     ch_num = nitems(iwm_nvm_channels_8000);
+             memset(bands, 0, sizeof(bands));
+             setbit(bands, IEEE80211_MODE_11A);
+             iwm_add_channel_band(sc, chans, maxchans, nchans,
+                 IWM_NUM_2GHZ_CHANNELS, ch_num, bands);
+     }
+}
+
+static void
+iwm_set_hw_address_family_8000(struct iwm_softc *sc, struct iwm_nvm_data 
*data,
+     const uint16_t *mac_override, const uint16_t *nvm_hw)
+{
+     const uint8_t *hw_addr;
+
+     if (mac_override) {
+             static const uint8_t reserved_mac[] = {
+                     0x02, 0xcc, 0xaa, 0xff, 0xee, 0x00
+             };
+
+             hw_addr = (const uint8_t *)(mac_override +
+                              IWM_MAC_ADDRESS_OVERRIDE_8000);
+
+             /*
+              * Store the MAC address from MAO section.
+              * No byte swapping is required in MAO section
+              */
+             IEEE80211_ADDR_COPY(data->hw_addr, hw_addr);
+
+             /*
+              * Force the use of the OTP MAC address in case of reserved MAC
+              * address in the NVM, or if address is given but invalid.
+              */
+             if (!IEEE80211_ADDR_EQ(reserved_mac, hw_addr) &&
+                 !IEEE80211_ADDR_EQ(ieee80211broadcastaddr, data->hw_addr) &&
+                 iwm_is_valid_ether_addr(data->hw_addr) &&
+                 !IEEE80211_IS_MULTICAST(data->hw_addr))
+                     return;
+
+             IWM_DPRINTF(sc, IWM_DEBUG_RESET,
+                 "%s: mac address from nvm override section invalid\n",
+                 __func__);
+     }
+
+     if (nvm_hw) {
+             /* read the mac address from WFMP registers */
+             uint32_t mac_addr0 =
+                 htole32(iwm_read_prph(sc, IWM_WFMP_MAC_ADDR_0));
+             uint32_t mac_addr1 =
+                 htole32(iwm_read_prph(sc, IWM_WFMP_MAC_ADDR_1));
+
+             hw_addr = (const uint8_t *)&mac_addr0;
+             data->hw_addr[0] = hw_addr[3];
+             data->hw_addr[1] = hw_addr[2];
+             data->hw_addr[2] = hw_addr[1];
+             data->hw_addr[3] = hw_addr[0];
+
+             hw_addr = (const uint8_t *)&mac_addr1;
+             data->hw_addr[4] = hw_addr[1];
+             data->hw_addr[5] = hw_addr[0];
+
+             return;
+     }
+
+     device_printf(sc->sc_dev, "%s: mac address not found\n", __func__);
+     memset(data->hw_addr, 0, sizeof(data->hw_addr));
+}
+
+static int
+iwm_get_sku(const struct iwm_softc *sc, const uint16_t *nvm_sw,
+         const uint16_t *phy_sku)
+{
+     if (sc->cfg->device_family != IWM_DEVICE_FAMILY_8000)
+             return le16_to_cpup(nvm_sw + IWM_SKU);
+
+     return le32_to_cpup((const uint32_t *)(phy_sku + IWM_SKU_8000));
+}
+
+static int
+iwm_get_nvm_version(const struct iwm_softc *sc, const uint16_t *nvm_sw)
+{
+     if (sc->cfg->device_family != IWM_DEVICE_FAMILY_8000)
+             return le16_to_cpup(nvm_sw + IWM_NVM_VERSION);
+     else
+             return le32_to_cpup((const uint32_t *)(nvm_sw +
+                                             IWM_NVM_VERSION_8000));
+}
+
+static int
+iwm_get_radio_cfg(const struct iwm_softc *sc, const uint16_t *nvm_sw,
+               const uint16_t *phy_sku)
+{
+        if (sc->cfg->device_family != IWM_DEVICE_FAMILY_8000)
+                return le16_to_cpup(nvm_sw + IWM_RADIO_CFG);
+
+        return le32_to_cpup((const uint32_t *)(phy_sku + 
IWM_RADIO_CFG_8000));
+}
+
+static int
+iwm_get_n_hw_addrs(const struct iwm_softc *sc, const uint16_t *nvm_sw)
+{
+     int n_hw_addr;
+
+     if (sc->cfg->device_family != IWM_DEVICE_FAMILY_8000)
+             return le16_to_cpup(nvm_sw + IWM_N_HW_ADDRS);
+
+     n_hw_addr = le32_to_cpup((const uint32_t *)(nvm_sw + 
IWM_N_HW_ADDRS_8000));
+
+        return n_hw_addr & IWM_N_HW_ADDR_MASK;
+}
+
+static void
+iwm_set_radio_cfg(const struct iwm_softc *sc, struct iwm_nvm_data *data,
+               uint32_t radio_cfg)
+{
+     if (sc->cfg->device_family != IWM_DEVICE_FAMILY_8000) {
+             data->radio_cfg_type = IWM_NVM_RF_CFG_TYPE_MSK(radio_cfg);
+             data->radio_cfg_step = IWM_NVM_RF_CFG_STEP_MSK(radio_cfg);
+             data->radio_cfg_dash = IWM_NVM_RF_CFG_DASH_MSK(radio_cfg);
+             data->radio_cfg_pnum = IWM_NVM_RF_CFG_PNUM_MSK(radio_cfg);
+             return;
+     }
+
+     /* set the radio configuration for family 8000 */
+     data->radio_cfg_type = IWM_NVM_RF_CFG_TYPE_MSK_8000(radio_cfg);
+     data->radio_cfg_step = IWM_NVM_RF_CFG_STEP_MSK_8000(radio_cfg);
+     data->radio_cfg_dash = IWM_NVM_RF_CFG_DASH_MSK_8000(radio_cfg);
+     data->radio_cfg_pnum = IWM_NVM_RF_CFG_FLAVOR_MSK_8000(radio_cfg);
+     data->valid_tx_ant = IWM_NVM_RF_CFG_TX_ANT_MSK_8000(radio_cfg);
+     data->valid_rx_ant = IWM_NVM_RF_CFG_RX_ANT_MSK_8000(radio_cfg);
+}
+
+static int
+iwm_set_hw_address(struct iwm_softc *sc, struct iwm_nvm_data *data,
+                const uint16_t *nvm_hw, const uint16_t *mac_override)
+{
+#ifdef notyet /* for FAMILY 9000 */
+     if (cfg->mac_addr_from_csr) {
+             iwm_set_hw_address_from_csr(sc, data);
+        } else
+#endif
+     if (sc->cfg->device_family != IWM_DEVICE_FAMILY_8000) {
+             const uint8_t *hw_addr = (const uint8_t *)(nvm_hw + 
IWM_HW_ADDR);
+
+             /* The byte order is little endian 16 bit, meaning 214365 */
+             data->hw_addr[0] = hw_addr[1];
+             data->hw_addr[1] = hw_addr[0];
+             data->hw_addr[2] = hw_addr[3];
+             data->hw_addr[3] = hw_addr[2];
+             data->hw_addr[4] = hw_addr[5];
+             data->hw_addr[5] = hw_addr[4];
+     } else {
+             iwm_set_hw_address_family_8000(sc, data, mac_override, nvm_hw);
+     }
+
+     if (!iwm_is_valid_ether_addr(data->hw_addr)) {
+             device_printf(sc->sc_dev, "no valid mac address was found\n");
+             return EINVAL;
+     }
+
+     return 0;
+}
+
+static struct iwm_nvm_data *
+iwm_parse_nvm_data(struct iwm_softc *sc,
+                const uint16_t *nvm_hw, const uint16_t *nvm_sw,
+                const uint16_t *nvm_calib, const uint16_t *mac_override,
+                const uint16_t *phy_sku, const uint16_t *regulatory)
+{
+     struct iwm_nvm_data *data;
+     uint32_t sku, radio_cfg;
+     uint16_t lar_config;
+
+     if (sc->cfg->device_family != IWM_DEVICE_FAMILY_8000) {
+             data = malloc(sizeof(*data) +
+                 IWM_NUM_CHANNELS * sizeof(uint16_t),
+                 M_DEVBUF, M_NOWAIT | M_ZERO);
+     } else {
+             data = malloc(sizeof(*data) +
+                 IWM_NUM_CHANNELS_8000 * sizeof(uint16_t),
+                 M_DEVBUF, M_NOWAIT | M_ZERO);
+     }
+     if (!data)
+             return NULL;
+
+     data->nvm_version = iwm_get_nvm_version(sc, nvm_sw);
+
+     radio_cfg = iwm_get_radio_cfg(sc, nvm_sw, phy_sku);
+     iwm_set_radio_cfg(sc, data, radio_cfg);
+
+     sku = iwm_get_sku(sc, nvm_sw, phy_sku);
+     data->sku_cap_band_24GHz_enable = sku & IWM_NVM_SKU_CAP_BAND_24GHZ;
+     data->sku_cap_band_52GHz_enable = sku & IWM_NVM_SKU_CAP_BAND_52GHZ;
+     data->sku_cap_11n_enable = 0;
+
+     data->n_hw_addrs = iwm_get_n_hw_addrs(sc, nvm_sw);
+
+     if (sc->cfg->device_family == IWM_DEVICE_FAMILY_8000) {
+             uint16_t lar_offset = data->nvm_version < 0xE39 ?
+                                    IWM_NVM_LAR_OFFSET_8000_OLD :
+                                    IWM_NVM_LAR_OFFSET_8000;
+
+             lar_config = le16_to_cpup(regulatory + lar_offset);
+             data->lar_enabled = !!(lar_config &
+                                    IWM_NVM_LAR_ENABLED_8000);
+     }
+
+     /* If no valid mac address was found - bail out */
+     if (iwm_set_hw_address(sc, data, nvm_hw, mac_override)) {
+             free(data, M_DEVBUF);
+             return NULL;
+     }
+
+     if (sc->cfg->device_family == IWM_DEVICE_FAMILY_7000) {
+             memcpy(data->nvm_ch_flags, &nvm_sw[IWM_NVM_CHANNELS],
+                 IWM_NUM_CHANNELS * sizeof(uint16_t));
+     } else {

[ *** diff truncated: 19117 lines dropped *** ]






Other related posts: