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 = 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 *** ]