summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--exception_lists/copyright7
-rw-r--r--exception_lists/cstyle3
-rw-r--r--exception_lists/keywords1
-rw-r--r--exception_lists/packaging2
-rw-r--r--manifest25
-rw-r--r--usr/src/Targetdirs2
-rw-r--r--usr/src/boot/lib/libstand/cd9660.c6
-rw-r--r--usr/src/boot/lib/libstand/globals.c2
-rw-r--r--usr/src/boot/lib/libstand/nfs.c12
-rw-r--r--usr/src/boot/lib/libstand/strtol.c2
-rw-r--r--usr/src/boot/lib/libstand/uuid_from_string.c2
-rw-r--r--usr/src/man/man4/system.417
-rw-r--r--usr/src/man/man7d/Makefile4
-rw-r--r--usr/src/man/man7d/iwn.7d59
-rw-r--r--usr/src/man/man9f/Makefile15
-rw-r--r--usr/src/man/man9f/firmload.9f152
-rw-r--r--usr/src/pkg/manifests/driver-network-iwn.mf88
-rw-r--r--usr/src/pkg/manifests/driver-network-iwp.mf9
-rw-r--r--usr/src/pkg/manifests/system-header.mf4
-rw-r--r--usr/src/pkg/manifests/system-kernel.man9f.inc8
-rw-r--r--usr/src/pkg/manifests/system-kernel.mf4
-rw-r--r--usr/src/uts/Makefile.targ9
-rw-r--r--usr/src/uts/Makefile.uts3
-rw-r--r--usr/src/uts/common/Makefile.files6
-rw-r--r--usr/src/uts/common/Makefile.rules7
-rw-r--r--usr/src/uts/common/io/iwn/THIRDPARTYLICENSE15
-rw-r--r--usr/src/uts/common/io/iwn/THIRDPARTYLICENSE.descrip1
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/THIRDPARTYLICENSE39
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/THIRDPARTYLICENSE.descrip1
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/iwlwifi-100-5.ucodebin0 -> 337572 bytes
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/iwlwifi-1000-3.ucodebin0 -> 335056 bytes
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/iwlwifi-105-6.ucodebin0 -> 689680 bytes
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/iwlwifi-135-6.ucodebin0 -> 701228 bytes
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/iwlwifi-2000-6.ucodebin0 -> 695876 bytes
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/iwlwifi-2030-6.ucodebin0 -> 707392 bytes
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/iwlwifi-4965-2.ucodebin0 -> 187972 bytes
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/iwlwifi-5000-2.ucodebin0 -> 353240 bytes
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/iwlwifi-5150-2.ucodebin0 -> 337400 bytes
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6000-4.ucodebin0 -> 462280 bytes
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6000g2a-5.ucodebin0 -> 444128 bytes
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6000g2b-6.ucodebin0 -> 679436 bytes
-rw-r--r--usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6050-5.ucodebin0 -> 469780 bytes
-rw-r--r--usr/src/uts/common/io/iwn/if_iwn.c7746
-rw-r--r--usr/src/uts/common/io/iwn/if_iwncompat.h162
-rw-r--r--usr/src/uts/common/io/iwn/if_iwnreg.h2006
-rw-r--r--usr/src/uts/common/io/iwn/if_iwnvar.h421
-rw-r--r--usr/src/uts/common/io/lofi.c159
-rw-r--r--usr/src/uts/common/io/net80211/net80211_amrr.c125
-rw-r--r--usr/src/uts/common/io/net80211/net80211_output.c4
-rw-r--r--usr/src/uts/common/io/net80211/net80211_proto.c9
-rw-r--r--usr/src/uts/common/os/firmload.c149
-rw-r--r--usr/src/uts/common/sys/Makefile3
-rw-r--r--usr/src/uts/common/sys/THIRDPARTYLICENSE.firmload26
-rw-r--r--usr/src/uts/common/sys/THIRDPARTYLICENSE.firmload.descrip1
-rw-r--r--usr/src/uts/common/sys/firmload.h62
-rw-r--r--usr/src/uts/common/sys/net80211.h5
-rw-r--r--usr/src/uts/common/sys/net80211_amrr.h73
-rw-r--r--usr/src/uts/intel/Makefile.intel1
-rw-r--r--usr/src/uts/intel/iwn/Makefile93
59 files changed, 11447 insertions, 103 deletions
diff --git a/exception_lists/copyright b/exception_lists/copyright
index 991690b079..b34b59af8e 100644
--- a/exception_lists/copyright
+++ b/exception_lists/copyright
@@ -441,6 +441,11 @@ usr/src/uts/common/gssapi/mechs/krb5/krb5/os/toffset.c
usr/src/uts/common/gssapi/mechs/krb5/mech/util_seed.c
usr/src/uts/common/gssapi/mechs/krb5/mech/util_seqnum.c
usr/src/uts/common/gssapi/mechs/krb5/mech/val_cred.c
+usr/src/uts/common/io/iwn/THIRDPARTYLICENSE
+usr/src/uts/common/io/iwn/THIRDPARTYLICENSE.descrip
+usr/src/uts/common/io/iwn/fw-iw/THIRDPARTYLICENSE
+usr/src/uts/common/io/iwn/fw-iw/THIRDPARTYLICENSE.descrip
+usr/src/uts/common/io/iwn/fw-iw/*.ucode
usr/src/uts/common/io/ixgbe/core/ixgbe_82598.c
usr/src/uts/common/io/ixgbe/core/ixgbe_82598.h
usr/src/uts/common/io/ixgbe/core/ixgbe_82599.c
@@ -468,6 +473,8 @@ usr/src/uts/common/io/ixgbe/core/ixgbe_x540.h
usr/src/uts/common/io/ixgbe/core/ixgbe_x550.c
usr/src/uts/common/io/ixgbe/core/ixgbe_x550.h
usr/src/uts/common/fs/zfs/THIRDPARTYLICENSE.lz4.descrip
+usr/src/uts/common/sys/THIRDPARTYLICENSE.firmload
+usr/src/uts/common/sys/THIRDPARTYLICENSE.firmload.descrip
usr/src/uts/common/sys/scsi/adapters/mpt_sas/mpi/THIRDPARTYLICENSE.descrip
usr/src/uts/common/sys/scsi/adapters/mpt_sas/mpi/THIRDPARTYLICENSE
usr/src/uts/common/sys/scsi/adapters/mpt_sas/mpi/mpi2.h
diff --git a/exception_lists/cstyle b/exception_lists/cstyle
index c00da4dd3f..dd9bbb02c2 100644
--- a/exception_lists/cstyle
+++ b/exception_lists/cstyle
@@ -1131,6 +1131,9 @@ usr/src/uts/common/io/i40e/core/i40e_register.h
usr/src/uts/common/io/i40e/core/i40e_status.h
usr/src/uts/common/io/i40e/core/i40e_type.h
usr/src/uts/common/io/i40e/core/i40e_virtchnl.h
+usr/src/uts/common/io/iwn/if_iwn.c
+usr/src/uts/common/io/iwn/if_iwnreg.h
+usr/src/uts/common/io/iwn/if_iwnvar.h
usr/src/uts/common/io/ixgbe/core/ixgbe_82598.c
usr/src/uts/common/io/ixgbe/core/ixgbe_82598.h
usr/src/uts/common/io/ixgbe/core/ixgbe_82599.c
diff --git a/exception_lists/keywords b/exception_lists/keywords
index 251b4339c1..05af94294f 100644
--- a/exception_lists/keywords
+++ b/exception_lists/keywords
@@ -39,3 +39,4 @@ usr/src/cmd/zonestat/zonestat/zonestat.c
usr/src/test/zfs-tests/tests/functional/cli_root/zpool_upgrade/blockfiles/zfs-pool-v15.dat.bz2
usr/src/test/zfs-tests/tests/functional/delegate/delegate_common.kshlib
usr/src/test/test-runner/cmd/run
+usr/src/uts/common/io/iwn/fw-iw/*.ucode
diff --git a/exception_lists/packaging b/exception_lists/packaging
index 88c7318180..5fba2183a2 100644
--- a/exception_lists/packaging
+++ b/exception_lists/packaging
@@ -25,6 +25,7 @@
# Copyright 2014 Garrett D'Amore <garrett@damore.org>
# Copyright 2014 Nexenta Systems, Inc. All rights reserved.
# Copyright 2016 Toomas Soome <tsoome@me.com>
+# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
#
#
@@ -353,6 +354,7 @@ usr/platform/sun4u/include/sys/fcode.h sparc
#
# Private net80211 headers
#
+usr/include/sys/net80211_amrr.h
usr/include/sys/net80211_crypto.h
usr/include/sys/net80211_ht.h
usr/include/sys/net80211_proto.h
diff --git a/manifest b/manifest
index da1d82bc60..0ddce5c74c 100644
--- a/manifest
+++ b/manifest
@@ -613,6 +613,7 @@ f kernel/drv/amd64/ipsecesp 0755 root sys
f kernel/drv/amd64/iptun 0755 root sys
f kernel/drv/amd64/iscsi 0755 root sys
f kernel/drv/amd64/iscsit 0755 root sys
+f kernel/drv/amd64/iwn 0755 root sys
f kernel/drv/amd64/iwscn 0755 root sys
f kernel/drv/amd64/ixgb 0755 root sys
f kernel/drv/amd64/ixgbe 0755 root sys
@@ -771,6 +772,7 @@ f kernel/drv/ipsecesp.conf 0644 root sys
f kernel/drv/iptun.conf 0644 root sys
f kernel/drv/iscsi.conf 0644 root sys
f kernel/drv/iscsit.conf 0644 root sys
+f kernel/drv/iwn.conf 0644 root sys
f kernel/drv/iwscn.conf 0644 root sys
f kernel/drv/ixgbe.conf 0644 root sys
f kernel/drv/keysock.conf 0644 root sys
@@ -858,6 +860,21 @@ d kernel/exec 0755 root sys
d kernel/exec/amd64 0755 root sys
f kernel/exec/amd64/elfexec 0755 root sys
f kernel/exec/amd64/intpexec 0755 root sys
+d kernel/firmware 0755 root sys
+d kernel/firmware/iwn 0755 root sys
+f kernel/firmware/iwn/iwlwifi-100-5.ucode 0666 root sys
+f kernel/firmware/iwn/iwlwifi-1000-3.ucode 0666 root sys
+f kernel/firmware/iwn/iwlwifi-105-6.ucode 0666 root sys
+f kernel/firmware/iwn/iwlwifi-135-6.ucode 0666 root sys
+f kernel/firmware/iwn/iwlwifi-2000-6.ucode 0666 root sys
+f kernel/firmware/iwn/iwlwifi-2030-6.ucode 0666 root sys
+f kernel/firmware/iwn/iwlwifi-4965-2.ucode 0666 root sys
+f kernel/firmware/iwn/iwlwifi-5000-2.ucode 0666 root sys
+f kernel/firmware/iwn/iwlwifi-5150-2.ucode 0666 root sys
+f kernel/firmware/iwn/iwlwifi-6000-4.ucode 0666 root sys
+f kernel/firmware/iwn/iwlwifi-6000g2a-5.ucode 0666 root sys
+f kernel/firmware/iwn/iwlwifi-6000g2b-6.ucode 0666 root sys
+f kernel/firmware/iwn/iwlwifi-6050-5.ucode 0666 root sys
d kernel/fs 0755 root sys
d kernel/fs/amd64 0755 root sys
f kernel/fs/amd64/autofs 0755 root sys
@@ -3624,6 +3641,7 @@ f usr/include/sys/fibre-channel/ulp/fcp_util.h 0644 root bin
f usr/include/sys/fibre-channel/ulp/fcsm.h 0644 root bin
f usr/include/sys/file.h 0644 root bin
f usr/include/sys/filio.h 0644 root bin
+f usr/include/sys/firmload.h 0644 root bin
f usr/include/sys/flock.h 0644 root bin
f usr/include/sys/flock_impl.h 0644 root bin
d usr/include/sys/fm 0755 root bin
@@ -18747,6 +18765,7 @@ f usr/share/man/man7d/ipmi.7d 0444 root bin
f usr/share/man/man7d/ipnet.7d 0444 root bin
f usr/share/man/man7d/iprb.7d 0444 root bin
f usr/share/man/man7d/iscsi.7d 0444 root bin
+f usr/share/man/man7d/iwn.7d 0444 root bin
f usr/share/man/man7d/ixgbe.7d 0444 root bin
f usr/share/man/man7d/kmdb.7d 0444 root bin
s usr/share/man/man7d/kmem.7d=mem.7d
@@ -19650,6 +19669,12 @@ f usr/share/man/man9f/dupmsg.9f 0444 root bin
f usr/share/man/man9f/enableok.9f 0444 root bin
f usr/share/man/man9f/esballoc.9f 0444 root bin
f usr/share/man/man9f/esbbcall.9f 0444 root bin
+s usr/share/man/man9f/firmware_close.9f.9f=firmload.9f
+s usr/share/man/man9f/firmware_free.9f.9f=firmload.9f
+s usr/share/man/man9f/firmware_get_size.9f.9f=firmload.9f
+s usr/share/man/man9f/firmware_malloc.9f.9f=firmload.9f
+s usr/share/man/man9f/firmware_open.9f.9f=firmload.9f
+s usr/share/man/man9f/firmware_read.9f.9f=firmload.9f
f usr/share/man/man9f/flushband.9f 0444 root bin
f usr/share/man/man9f/flushq.9f 0444 root bin
s usr/share/man/man9f/free_pktiopb.9f=get_pktiopb.9f
diff --git a/usr/src/Targetdirs b/usr/src/Targetdirs
index 2f815d5576..22d9395685 100644
--- a/usr/src/Targetdirs
+++ b/usr/src/Targetdirs
@@ -27,6 +27,7 @@
# Copyright (c) 2013 RackTop Systems.
# Copyright 2014 Garrett D'Amore <garrett@damore.org>
# Copyright 2014 Nexenta Systems, Inc. All rights reserved.
+# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
#
#
@@ -202,6 +203,7 @@ DIRS= \
/lib/svc/seed \
/lib/svc/share \
/kernel \
+ /kernel/firmware \
/mnt \
/opt \
/platform \
diff --git a/usr/src/boot/lib/libstand/cd9660.c b/usr/src/boot/lib/libstand/cd9660.c
index e03f56eef4..c28a8b222d 100644
--- a/usr/src/boot/lib/libstand/cd9660.c
+++ b/usr/src/boot/lib/libstand/cd9660.c
@@ -273,13 +273,13 @@ dirmatch(struct open_file *f, const char *path, struct iso_directory_record *dp,
static int
cd9660_open(const char *path, struct open_file *f)
{
- struct file *fp = 0;
+ struct file *fp = NULL;
void *buf;
struct iso_primary_descriptor *vd;
size_t buf_size, read, dsize, off;
daddr_t bno, boff;
struct iso_directory_record rec;
- struct iso_directory_record *dp = 0;
+ struct iso_directory_record *dp = NULL;
int rc, first, use_rrip, lenskip;
/* First find the volume descriptor */
@@ -421,7 +421,7 @@ cd9660_close(struct open_file *f)
{
struct file *fp = (struct file *)f->f_fsdata;
- f->f_fsdata = 0;
+ f->f_fsdata = NULL;
free(fp);
return 0;
diff --git a/usr/src/boot/lib/libstand/globals.c b/usr/src/boot/lib/libstand/globals.c
index 03108232ff..5966c99ae9 100644
--- a/usr/src/boot/lib/libstand/globals.c
+++ b/usr/src/boot/lib/libstand/globals.c
@@ -30,7 +30,7 @@ struct in_addr myip; /* my ip address */
struct in_addr nameip; /* DNS server ip address */
struct in_addr rootip; /* root ip address */
struct in_addr swapip; /* swap ip address */
-struct in_addr gateip; /* swap ip address */
+struct in_addr gateip; /* gateway ip address */
n_long netmask = 0xffffff00; /* subnet or net mask */
int errno; /* our old friend */
diff --git a/usr/src/boot/lib/libstand/nfs.c b/usr/src/boot/lib/libstand/nfs.c
index a0b726cde3..222b2f50af 100644
--- a/usr/src/boot/lib/libstand/nfs.c
+++ b/usr/src/boot/lib/libstand/nfs.c
@@ -511,7 +511,7 @@ nfs_open(const char *upath, struct open_file *f)
#ifndef NFS_NOSYMLINK
bcopy(&nfs_root_node, currfd, sizeof(*currfd));
- newfd = 0;
+ newfd = NULL;
cp = path = strdup(upath);
if (path == NULL) {
@@ -593,14 +593,14 @@ nfs_open(const char *upath, struct open_file *f)
bcopy(&nfs_root_node, currfd, sizeof(*currfd));
free(newfd);
- newfd = 0;
+ newfd = NULL;
continue;
}
free(currfd);
currfd = newfd;
- newfd = 0;
+ newfd = NULL;
}
error = 0;
@@ -1155,7 +1155,7 @@ nfs_open(const char *upath, struct open_file *f)
}
#ifndef NFS_NOSYMLINK
bcopy(&nfs_root_node, currfd, sizeof(*currfd));
- newfd = 0;
+ newfd = NULL;
cp = path = strdup(upath);
if (path == NULL) {
@@ -1241,14 +1241,14 @@ nfs_open(const char *upath, struct open_file *f)
bcopy(&nfs_root_node, currfd, sizeof(*currfd));
free(newfd);
- newfd = 0;
+ newfd = NULL;
continue;
}
free(currfd);
currfd = newfd;
- newfd = 0;
+ newfd = NULL;
}
error = 0;
diff --git a/usr/src/boot/lib/libstand/strtol.c b/usr/src/boot/lib/libstand/strtol.c
index ec5daf0bb8..be82fb1b67 100644
--- a/usr/src/boot/lib/libstand/strtol.c
+++ b/usr/src/boot/lib/libstand/strtol.c
@@ -126,7 +126,7 @@ strtol(nptr, endptr, base)
errno = ERANGE;
} else if (neg)
acc = -acc;
- if (endptr != 0)
+ if (endptr != NULL)
*endptr = (char *)(any ? s - 1 : nptr);
return (acc);
}
diff --git a/usr/src/boot/lib/libstand/uuid_from_string.c b/usr/src/boot/lib/libstand/uuid_from_string.c
index 2d5a6dd197..7a59b4189e 100644
--- a/usr/src/boot/lib/libstand/uuid_from_string.c
+++ b/usr/src/boot/lib/libstand/uuid_from_string.c
@@ -57,7 +57,7 @@ fromhex(const char *s, int len, int *ok)
if (!*ok)
return 0;
v = 0;
- for (i = 0; i < 8; i++) {
+ for (i = 0; i < len; i++) {
h = hex2int(s[i]);
if (h == 16) {
*ok = 0;
diff --git a/usr/src/man/man4/system.4 b/usr/src/man/man4/system.4
index 6430dcc839..ebd0d96faa 100644
--- a/usr/src/man/man4/system.4
+++ b/usr/src/man/man4/system.4
@@ -1,14 +1,14 @@
'\" te
.\" Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+.\" Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
.\" Copyright 1989 AT&T
.\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License"). You may not use this file except in compliance with the License.
.\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing. See the License for the specific language governing permissions and limitations under the License.
.\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE. If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner]
-.TH SYSTEM 4 "Nov 3, 2004"
+.TH SYSTEM 4 "Jan 25, 2016"
.SH NAME
system \- system configuration information file
.SH DESCRIPTION
-.sp
.LP
The \fBsystem\fR file is used for customizing the operation of the operating
system kernel. The recommended procedure is to preserve the original
@@ -102,6 +102,16 @@ javaexec
.sp
.ne 2
.na
+\fB\fBfirmware\fR\fR
+.ad
+.RS 10n
+Raw firmware images in subdirectories, one for each device driver
+module using \fBfirmload\fR(9F).
+.RE
+
+.sp
+.ne 2
+.na
\fB\fBfs\fR\fR
.ad
.RS 10n
@@ -337,11 +347,9 @@ set moddebug | 0x40
.sp
.SH SEE ALSO
-.sp
.LP
\fBboot\fR(1M), \fBinit\fR(1M), \fBkernel\fR(1M)
.SH WARNINGS
-.sp
.LP
Use care when modifying the \fBsystem\fR file; it modifies the operation of the
kernel. If you preserved the original \fBsystem\fR file, you can boot using
@@ -351,6 +359,5 @@ file that will work, you may specify \fB/dev/null\fR. This acts as an empty
\fBsystem\fR file, and the system will attempt to boot using its default
settings.
.SH NOTES
-.sp
.LP
The \fB/etc/system\fR file is read only once, at boot time.
diff --git a/usr/src/man/man7d/Makefile b/usr/src/man/man7d/Makefile
index ac6a59b588..b7ded478a7 100644
--- a/usr/src/man/man7d/Makefile
+++ b/usr/src/man/man7d/Makefile
@@ -15,6 +15,7 @@
# Copyright 2016 Joyent, Inc.
# Copyright 2016 Garrett D'Amore <garrett@damore.org>
# Copyright 2016 Joyent, Inc.
+# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
#
include $(SRC)/Makefile.master
@@ -220,6 +221,7 @@ i386_MANFILES= ahci.7d \
ipw.7d \
iwh.7d \
iwi.7d \
+ iwn.7d \
mega_sas.7d \
npe.7d \
ntxn.7d \
@@ -236,7 +238,7 @@ i386_MANFILES= ahci.7d \
sdhost.7d \
sfxge.7d \
si3124.7d \
- skd.7d \
+ skd.7d \
smbios.7d \
uath.7d \
ural.7d \
diff --git a/usr/src/man/man7d/iwn.7d b/usr/src/man/man7d/iwn.7d
new file mode 100644
index 0000000000..3769adb11d
--- /dev/null
+++ b/usr/src/man/man7d/iwn.7d
@@ -0,0 +1,59 @@
+.\"
+.\" This file and its contents are supplied under the terms of the
+.\" Common Development and Distribution License ("CDDL"), version 1.0.
+.\" You may only use this file in accordance with the terms of version
+.\" 1.0 of the CDDL.
+.\"
+.\" A full copy of the text of the CDDL should have accompanied this
+.\" source. A copy of the CDDL is also available via the Internet at
+.\" http://www.illumos.org/license/CDDL.
+.\"
+.\"
+.\" Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+.\"
+.Dd Jun 11, 2016
+.Dt IWN 7D
+.Os
+.Sh NAME
+.Nm iwn
+.Nd Intel(R) WiFi Link 4965 and 100/1000/2000/5000/6000 Series Driver
+.Sh DESCRIPTION
+The
+.Nm
+.Em 802.11a/g/n
+wireless NIC driver is a multi-threaded, loadable, clonable,
+GLDv3-based STREAMS driver supporting the Intel Wifi Link 4965, 100,
+1000, 2000, 5000, and 6000 series of NIC's.
+Driver functions include controller initialization, wireless
+.Em 802.11
+infrastructure network connection, WEP and frame transmit and receive.
+.Sh CONFIGURATION
+The
+.Nm
+driver performs auto-negotiation to determine the data rate and mode
+Supported data rates are 1, 2, 5.5, and 11 Mbit/sec for
+.Em 802.11b
+and
+.Em 802.11g ,
+as well as 6, 9, 12, 18, 24, 36, 48, and 54 Mbit/sec for
+.Em 802.11a
+and
+.Em 802.11g .
+.Sh FILES
+.Bl -tag -compact -width Pa
+.It Pa /dev/iwn
+Special character device.
+.It Pa /kernel/drv/iwn
+32-bit ELF kernel module (x86).
+.It Pa /kernel/drv/amd64/iwn
+64-bit ELF kernel module (x86).
+.It Pa /kernel/firmware/iwn/*.ucode
+.Nm
+firmware files
+.El
+.Sh SEE ALSO
+.Xr dladm 1M ,
+.Xr wificonfig 1M
+.Pp
+.Em 802.11 - Wireless LAN Media Access Control and Physical Layer
+Specification- IEEE, 2001
diff --git a/usr/src/man/man9f/Makefile b/usr/src/man/man9f/Makefile
index b72191c28d..42e5447e16 100644
--- a/usr/src/man/man9f/Makefile
+++ b/usr/src/man/man9f/Makefile
@@ -14,6 +14,7 @@
# Copyright 2014 Garrett D'Amore <garrett@damore>
# Copyright 2016 Joyent, Inc.
# Copyright 2016 Nexenta Systems, Inc.
+# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
#
include $(SRC)/Makefile.master
@@ -264,6 +265,7 @@ MANFILES= ASSERT.9f \
enableok.9f \
esballoc.9f \
esbbcall.9f \
+ firmload.9f \
flushband.9f \
flushq.9f \
freeb.9f \
@@ -897,6 +899,12 @@ MANLINKS= SIZEOF_PTR.9f \
dlokack.9f \
dlphysaddrack.9f \
dluderrorind.9f \
+ firmware_close.9f \
+ firmware_free.9f \
+ firmware_get_size.9f \
+ firmware_malloc.9f \
+ firmware_open.9f \
+ firmware_read.9f \
free_pktiopb.9f \
gld_intr.9f \
gld_mac_alloc.9f \
@@ -1682,6 +1690,13 @@ desballoc.9f := LINKSRC = esballoc.9f
unfreezestr.9f := LINKSRC = freezestr.9f
+firmware_close.9f := LINKSRC = firmload.9f
+firmware_free.9f := LINKSRC = firmload.9f
+firmware_get_size.9f := LINKSRC = firmload.9f
+firmware_malloc.9f := LINKSRC = firmload.9f
+firmware_open.9f := LINKSRC = firmload.9f
+firmware_read.9f := LINKSRC = firmload.9f
+
free_pktiopb.9f := LINKSRC = get_pktiopb.9f
gld_intr.9f := LINKSRC = gld.9f
diff --git a/usr/src/man/man9f/firmload.9f b/usr/src/man/man9f/firmload.9f
new file mode 100644
index 0000000000..0641fe3b2e
--- /dev/null
+++ b/usr/src/man/man9f/firmload.9f
@@ -0,0 +1,152 @@
+.\" $NetBSD: firmload.9,v 1.8 2014/03/18 18:20:40 riastradh Exp $
+.\"
+.\" Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+.\"
+.\" Copyright (c) 2006 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Jason R. Thorpe.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. 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.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+.\"
+.Dd January 22, 2016
+.Dt FIRMLOAD 9F
+.Os
+.Sh NAME
+.Nm firmload
+.Nd Firmware loader API for device drivers
+.Sh SYNOPSIS
+.In sys/firmload.h
+.Ft int
+.Fo "firmware_open"
+.Fa "const char *drvname"
+.Fa "const char *imgname"
+.Fa "firmware_handle_t *fhp"
+.Fc
+.Ft int
+.Fo "firmware_close"
+.Fa "firmware_handle_t fh"
+.Fc
+.Ft off_t
+.Fo "firmware_get_size"
+.Fa "firmware_handle_t fh"
+.Fc
+.Ft int
+.Fo "firmware_read"
+.Fa "firmware_handle_t fh"
+.Fa "off_t offset"
+.Fa "void *buf"
+.Fa "size_t size"
+.Fc
+.Sh PARAMETERS
+.Bl -tag -width Va
+.It Fa drvname
+The name of the driver using
+.Nm .
+This is used as the subdirectory holding the firmware images.
+.It Fa imgname
+The file name of a firmware image.
+.It Fa fhp
+The pointer used for returing a firmware handle.
+.It Fa fh
+The firmware handle.
+.It Fa offset
+The offset in the firmware image to start reading from.
+.It Fa buf
+Pointer to a buffer to hold the firmware data.
+.It Fa size
+Size of the buffer to hold the firmware data.
+.El
+.Sh DESCRIPTION
+.Nm
+provides a simple and convenient API for device drivers to load firmware
+images from files residing in the file system that are necessary for the
+devices that they control.
+It is primarily intended for devices without non-volatile firmware
+memory, which usually require the driver to load a firmware image at
+attach time.
+Firmware images reside in sub-directories, one for each driver, in the
+namespace "firmware" in the system default module search path as
+described in
+.Xr system 4 .
+.sp
+The following functions are provided by the
+.Nm
+API:
+.Bl -tag -width indent
+.It Fn "firmware_open"
+Open the firmware image
+.Fa imgname
+for the driver
+.Fa drvname .
+The path to the firmware image file is constructed by appending the string
+.Dq "firmware/drvname/imgname"
+to each system module path prefix until opening the firmware image
+file succeeds.
+.It Fn "firmware_close"
+Close the firmware image file associated with the firmware handle
+.Fa fh .
+.It Fn "firmware_get_size"
+Returns the size of the image file associated with the firmware handle
+.Fa fh .
+.It Fn "firmware_read"
+Reads from the image file associated with the firmware handle
+.Fa fh
+beginning at offset
+.Fa offset
+for length
+.Fa size .
+The firmware image data is placed into the buffer specified by
+.Fa buf .
+.Fn "firmware_read"
+will either read as much data as requested or fail, there are no short
+reads.
+.El
+.Sh CONTEXT
+These functions can be called from user and kernel context.
+.Sh RETURN VALUES
+Upon successful completion, the
+.Fn firmware_open
+function returns zero and stores a firmware handle in
+.Fa fhp .
+Otherwise a non-zero error code is returned.
+.sp
+The function
+.Fn firmware_read
+will return zero on success and
+.Fa buf
+will be filled with
+.Fa size
+bytes of data.
+On failure -1 is returned.
+.sp
+The function
+.Fn firmware_get_size
+returns the size of a firmware image.
+.sp
+.Fn firmware_close
+will always return zero.
+.Sh INTERFACE STABILITY
+.Sy Committed
+.Sh SEE ALSO
+.Xr system 4
diff --git a/usr/src/pkg/manifests/driver-network-iwn.mf b/usr/src/pkg/manifests/driver-network-iwn.mf
new file mode 100644
index 0000000000..18da9add2b
--- /dev/null
+++ b/usr/src/pkg/manifests/driver-network-iwn.mf
@@ -0,0 +1,88 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+#
+
+#
+# The default for payload-bearing actions in this package is to appear in the
+# global zone only. See the include file for greater detail, as well as
+# information about overriding the defaults.
+#
+<include global_zone_only_component>
+set name=pkg.fmri value=pkg:/driver/network/iwn@$(PKGVERS)
+set name=pkg.description \
+ value="Intel(R) WiFi Link 4965 and 100/1000/2000/5000/6000 series driver"
+set name=pkg.summary \
+ value="Intel(R) WiFi Link 4965 and 100/1000/2000/5000/6000 series driver"
+set name=info.classification \
+ value=org.opensolaris.category.2008:Drivers/Networking
+set name=variant.arch value=i386
+dir path=kernel group=sys
+dir path=kernel/drv group=sys
+dir path=kernel/drv/$(ARCH64) group=sys
+dir path=kernel/firmware group=sys
+dir path=kernel/firmware/iwn group=sys
+dir path=usr/share/man
+dir path=usr/share/man/man7d
+driver name=iwn clone_perms="iwn 0666 root sys" perms="* 0666 root sys" \
+ alias=pciex8086,0082 \
+ alias=pciex8086,0083 \
+ alias=pciex8086,0084 \
+ alias=pciex8086,0085 \
+ alias=pciex8086,0087 \
+ alias=pciex8086,0089 \
+ alias=pciex8086,008a \
+ alias=pciex8086,008b \
+ alias=pciex8086,008d \
+ alias=pciex8086,008e \
+ alias=pciex8086,0090 \
+ alias=pciex8086,0091 \
+ alias=pciex8086,0887 \
+ alias=pciex8086,0888 \
+ alias=pciex8086,088e \
+ alias=pciex8086,088f \
+ alias=pciex8086,0890 \
+ alias=pciex8086,0891 \
+ alias=pciex8086,0892 \
+ alias=pciex8086,0893 \
+ alias=pciex8086,0894 \
+ alias=pciex8086,0895 \
+ alias=pciex8086,0896 \
+ alias=pciex8086,0897 \
+ alias=pciex8086,08ae \
+ alias=pciex8086,08af \
+ alias=pciex8086,422b \
+ alias=pciex8086,422c \
+ alias=pciex8086,4238 \
+ alias=pciex8086,4239
+file path=kernel/drv/$(ARCH64)/iwn group=sys
+file path=kernel/drv/iwn group=sys
+file path=kernel/firmware/iwn/iwlwifi-100-5.ucode
+file path=kernel/firmware/iwn/iwlwifi-1000-3.ucode
+file path=kernel/firmware/iwn/iwlwifi-105-6.ucode
+file path=kernel/firmware/iwn/iwlwifi-135-6.ucode
+file path=kernel/firmware/iwn/iwlwifi-2000-6.ucode
+file path=kernel/firmware/iwn/iwlwifi-2030-6.ucode
+file path=kernel/firmware/iwn/iwlwifi-4965-2.ucode
+file path=kernel/firmware/iwn/iwlwifi-5000-2.ucode
+file path=kernel/firmware/iwn/iwlwifi-5150-2.ucode
+file path=kernel/firmware/iwn/iwlwifi-6000-4.ucode
+file path=kernel/firmware/iwn/iwlwifi-6000g2a-5.ucode
+file path=kernel/firmware/iwn/iwlwifi-6000g2b-6.ucode
+file path=kernel/firmware/iwn/iwlwifi-6050-5.ucode
+file path=usr/share/man/man7d/iwn.7d
+license lic_CDDL license=lic_CDDL
+license usr/src/uts/common/io/iwn/THIRDPARTYLICENSE \
+ license=usr/src/uts/common/io/iwn/THIRDPARTYLICENSE
+license usr/src/uts/common/io/iwn/fw-iw/THIRDPARTYLICENSE \
+ license=usr/src/uts/common/io/iwn/fw-iw/THIRDPARTYLICENSE
diff --git a/usr/src/pkg/manifests/driver-network-iwp.mf b/usr/src/pkg/manifests/driver-network-iwp.mf
index eb1a8cd6a7..f8c83ba399 100644
--- a/usr/src/pkg/manifests/driver-network-iwp.mf
+++ b/usr/src/pkg/manifests/driver-network-iwp.mf
@@ -21,6 +21,7 @@
#
# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
#
#
@@ -38,13 +39,7 @@ set name=variant.arch value=i386
dir path=kernel group=sys
dir path=kernel/drv group=sys
dir path=kernel/drv/$(ARCH64) group=sys
-driver name=iwp clone_perms="iwp 0666 root sys" perms="* 0666 root sys" \
- alias=pciex8086,008d \
- alias=pciex8086,008e \
- alias=pciex8086,422b \
- alias=pciex8086,422c \
- alias=pciex8086,4238 \
- alias=pciex8086,4239
+driver name=iwp clone_perms="iwp 0666 root sys" perms="* 0666 root sys"
file path=kernel/drv/$(ARCH64)/iwp group=sys
file path=kernel/drv/iwp group=sys
legacy pkg=SUNWiwp desc="Intel(R) WiFi Link 6000 series driver" \
diff --git a/usr/src/pkg/manifests/system-header.mf b/usr/src/pkg/manifests/system-header.mf
index 9a54045296..e1bf69f00c 100644
--- a/usr/src/pkg/manifests/system-header.mf
+++ b/usr/src/pkg/manifests/system-header.mf
@@ -25,6 +25,7 @@
# Copyright 2013 Saso Kiselkov. All rights reserved.
# Copyright 2014 Garrett D'Amore <garrett@damore.org>
# Copyright 2016 Nexenta Systems, Inc.
+# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
#
set name=pkg.fmri value=pkg:/system/header@$(PKGVERS)
@@ -996,6 +997,7 @@ file path=usr/include/sys/feature_tests.h
file path=usr/include/sys/fem.h
file path=usr/include/sys/file.h
file path=usr/include/sys/filio.h
+file path=usr/include/sys/firmload.h
file path=usr/include/sys/flock.h
file path=usr/include/sys/flock_impl.h
$(sparc_ONLY)file path=usr/include/sys/fm/cpu/SPARC64-VI.h
@@ -1981,6 +1983,8 @@ license lic_CDDL license=lic_CDDL
license license_in_headers license=license_in_headers
license usr/src/lib/pkcs11/include/THIRDPARTYLICENSE \
license=usr/src/lib/pkcs11/include/THIRDPARTYLICENSE
+license usr/src/uts/common/sys/THIRDPARTYLICENSE.firmload \
+ license=usr/src/uts/common/sys/THIRDPARTYLICENSE.firmload
link path=usr/include/iso/assert_iso.h target=../assert.h
link path=usr/include/iso/errno_iso.h target=../errno.h
link path=usr/include/iso/float_iso.h target=../float.h
diff --git a/usr/src/pkg/manifests/system-kernel.man9f.inc b/usr/src/pkg/manifests/system-kernel.man9f.inc
index 972e2e58a1..3fb8b3c58b 100644
--- a/usr/src/pkg/manifests/system-kernel.man9f.inc
+++ b/usr/src/pkg/manifests/system-kernel.man9f.inc
@@ -13,6 +13,7 @@
# Copyright 2011, Richard Lowe
# Copyright 2014 Garrett D'Amore <garrett@damore.org>
# Copyright 2016 Nexenta Systems, Inc.
+# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
#
file path=usr/share/man/man9f/ASSERT.9f
@@ -259,6 +260,7 @@ file path=usr/share/man/man9f/dupmsg.9f
file path=usr/share/man/man9f/enableok.9f
file path=usr/share/man/man9f/esballoc.9f
file path=usr/share/man/man9f/esbbcall.9f
+file path=usr/share/man/man9f/firmload.9f
file path=usr/share/man/man9f/flushband.9f
file path=usr/share/man/man9f/flushq.9f
file path=usr/share/man/man9f/freeb.9f
@@ -907,6 +909,12 @@ link path=usr/share/man/man9f/dlokack.9f target=dlbindack.9f
link path=usr/share/man/man9f/dlphysaddrack.9f target=dlbindack.9f
link path=usr/share/man/man9f/dluderrorind.9f target=dlbindack.9f
link path=usr/share/man/man9f/free_pktiopb.9f target=get_pktiopb.9f
+link path=usr/share/man/man9f/firmware_close.9f target=firmload.9f
+link path=usr/share/man/man9f/firmware_free.9f target=firmload.9f
+link path=usr/share/man/man9f/firmware_get_size.9f target=firmload.9f
+link path=usr/share/man/man9f/firmware_malloc.9f target=firmload.9f
+link path=usr/share/man/man9f/firmware_open.9f target=firmload.9f
+link path=usr/share/man/man9f/firmware_read.9f target=firmload.9f
link path=usr/share/man/man9f/gld_intr.9f target=gld.9f
link path=usr/share/man/man9f/gld_mac_alloc.9f target=gld.9f
link path=usr/share/man/man9f/gld_mac_free.9f target=gld.9f
diff --git a/usr/src/pkg/manifests/system-kernel.mf b/usr/src/pkg/manifests/system-kernel.mf
index a00bb109cc..9a2c454757 100644
--- a/usr/src/pkg/manifests/system-kernel.mf
+++ b/usr/src/pkg/manifests/system-kernel.mf
@@ -23,6 +23,7 @@
# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
# Copyright 2013 Saso Kiselkov. All rights reserved.
# Copyright 2015 Nexenta Systems, Inc. All rights reserved.
+# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
#
#
@@ -69,6 +70,7 @@ dir path=kernel/drv group=sys
dir path=kernel/drv/$(ARCH64) group=sys
dir path=kernel/exec group=sys
dir path=kernel/exec/$(ARCH64) group=sys
+dir path=kernel/firmware group=sys
dir path=kernel/fs group=sys
dir path=kernel/fs/$(ARCH64) group=sys
dir path=kernel/ipp group=sys
@@ -921,6 +923,8 @@ license usr/src/uts/common/inet/tcp/THIRDPARTYLICENSE \
license=usr/src/uts/common/inet/tcp/THIRDPARTYLICENSE
license usr/src/uts/common/io/THIRDPARTYLICENSE.etheraddr \
license=usr/src/uts/common/io/THIRDPARTYLICENSE.etheraddr
+license usr/src/uts/common/sys/THIRDPARTYLICENSE.firmload \
+ license=usr/src/uts/common/sys/THIRDPARTYLICENSE.firmload
license usr/src/uts/common/sys/THIRDPARTYLICENSE.icu \
license=usr/src/uts/common/sys/THIRDPARTYLICENSE.icu
license usr/src/uts/common/sys/THIRDPARTYLICENSE.unicode \
diff --git a/usr/src/uts/Makefile.targ b/usr/src/uts/Makefile.targ
index 72ca278100..92955e139f 100644
--- a/usr/src/uts/Makefile.targ
+++ b/usr/src/uts/Makefile.targ
@@ -21,6 +21,7 @@
#
# Copyright (c) 1991, 2010, Oracle and/or its affiliates. All rights reserved.
# Copyright 2015, Joyent, Inc.
+# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
#
# This Makefiles contains the common targets and definitions for
# all kernels. It is to be included in the Makefiles for specific
@@ -248,6 +249,14 @@ $(USR_BRAND_DIR)/%: $(OBJS_DIR)/% $(USR_BRAND_DIR) FRC
$(ROOT_KICONV_DIR)/%: $(OBJS_DIR)/% $(ROOT_KICONV_DIR) FRC
$(INS.file)
+$(ROOT_FIRMWARE_DIR)/$(MODULE):
+ $(INS.dir)
+
+$(ROOT_FIRMWARE_DIR)/$(MODULE)/%:= FILEMODE = $(CFILEMODE)
+
+$(ROOT_FIRMWARE_DIR)/$(MODULE)/%: $(ROOT_FIRMWARE_DIR)/$(MODULE) $(FWDIR)/%
+ $(INS.file)
+
include $(SRC)/Makefile.psm.targ
#
diff --git a/usr/src/uts/Makefile.uts b/usr/src/uts/Makefile.uts
index 273949fb74..f1bf541057 100644
--- a/usr/src/uts/Makefile.uts
+++ b/usr/src/uts/Makefile.uts
@@ -25,6 +25,7 @@
# Copyright (c) 2011 by Delphix. All rights reserved.
# Copyright (c) 2013 Andrew Stormont. All rights reserved.
# Copyright 2015 Joyent, Inc.
+# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
#
#
@@ -472,6 +473,7 @@ ROOT_CRYPTO_DIR = $(ROOT_CRYPTO_DIR_$(CLASS))
ROOT_MAC_DIR = $(ROOT_MAC_DIR_$(CLASS))
ROOT_OVERLAY_DIR = $(ROOT_OVERLAY_DIR_$(CLASS))
ROOT_KICONV_DIR = $(ROOT_KICONV_DIR_$(CLASS))
+ROOT_FIRMWARE_DIR = $(ROOT_MOD_DIR)/firmware
ROOT_MOD_DIRS_32 = $(ROOT_BRAND_DIR_32) $(ROOT_DRV_DIR_32)
ROOT_MOD_DIRS_32 = $(ROOT_BRAND_DIR_32) $(ROOT_DRV_DIR_32)
@@ -490,6 +492,7 @@ ROOT_MOD_DIRS_32 += $(ROOT_TOD_DIR_32) $(ROOT_DACF_DIR_32)
ROOT_MOD_DIRS_32 += $(ROOT_CRYPTO_DIR_32) $(ROOT_MAC_DIR_32)
ROOT_MOD_DIRS_32 += $(ROOT_OVERLAY_DIR_32)
ROOT_MOD_DIRS_32 += $(ROOT_KICONV_DIR_32)
+ROOT_MOD_DIRS_32 += $(ROOT_FIRMWARE_DIR)
USR_MOD_DIR = $(ROOT)/usr/kernel
diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files
index 5122b8bd21..b7f4a1d428 100644
--- a/usr/src/uts/common/Makefile.files
+++ b/usr/src/uts/common/Makefile.files
@@ -28,6 +28,7 @@
# Copyright 2016 Garrett D'Amore <garrett@damore.org>
# Copyright 2016 Joyent, Inc.
# Copyright 2016 OmniTI Computer Consulting, Inc. All rights reserved.
+# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
#
#
@@ -179,6 +180,7 @@ GENUNIX_OBJS += \
fem.o \
ffs.o \
fio.o \
+ firmload.o \
flock.o \
fm.o \
fork.o \
@@ -692,7 +694,7 @@ NET80211_OBJS += net80211.o net80211_proto.o net80211_input.o \
net80211_output.o net80211_node.o net80211_crypto.o \
net80211_crypto_none.o net80211_crypto_wep.o net80211_ioctl.o \
net80211_crypto_tkip.o net80211_crypto_ccmp.o \
- net80211_ht.o
+ net80211_ht.o net80211_amrr.o
VNIC_OBJS += vnic_ctl.o vnic_dev.o
@@ -1891,6 +1893,8 @@ IWH_OBJS += iwh.o
IWK_OBJS += iwk2.o
+IWN_OBJS += if_iwn.o
+
IWP_OBJS += iwp.o
MWL_OBJS += mwl.o
diff --git a/usr/src/uts/common/Makefile.rules b/usr/src/uts/common/Makefile.rules
index 2fca9300af..3c0ee2612b 100644
--- a/usr/src/uts/common/Makefile.rules
+++ b/usr/src/uts/common/Makefile.rules
@@ -913,6 +913,10 @@ $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/iwk/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
+$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/iwn/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/iwp/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
@@ -2340,6 +2344,9 @@ $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/iwi/%.c
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/iwk/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
+$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/iwn/%.c
+ @($(LHEAD) $(LINT.c) $< $(LTAIL))
+
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/iwp/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
diff --git a/usr/src/uts/common/io/iwn/THIRDPARTYLICENSE b/usr/src/uts/common/io/iwn/THIRDPARTYLICENSE
new file mode 100644
index 0000000000..68cc7c64ab
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/THIRDPARTYLICENSE
@@ -0,0 +1,15 @@
+/*-
+ * Copyright (c) 2007-2010 Damien Bergamini <damien.bergamini@free.fr>
+ *
+ * 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.
+ */
diff --git a/usr/src/uts/common/io/iwn/THIRDPARTYLICENSE.descrip b/usr/src/uts/common/io/iwn/THIRDPARTYLICENSE.descrip
new file mode 100644
index 0000000000..89fb111813
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/THIRDPARTYLICENSE.descrip
@@ -0,0 +1 @@
+Intel WiFi Link 4965 and 100/1000/2000/5000/6000 Series Device Driver
diff --git a/usr/src/uts/common/io/iwn/fw-iw/THIRDPARTYLICENSE b/usr/src/uts/common/io/iwn/fw-iw/THIRDPARTYLICENSE
new file mode 100644
index 0000000000..c235164235
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/THIRDPARTYLICENSE
@@ -0,0 +1,39 @@
+Copyright (c) 2006-2012, Intel Corporation.
+All rights reserved.
+
+Redistribution. Redistribution and use in binary form, without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions must reproduce the above copyright notice and the
+ following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+* Neither the name of Intel Corporation nor the names of its suppliers
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+* No reverse engineering, decompilation, or disassembly of this software
+ is permitted.
+
+Limited patent license. Intel Corporation grants a world-wide,
+royalty-free, non-exclusive license under patents it now or hereafter
+owns or controls to make, have made, use, import, offer to sell and
+sell ("Utilize") this software, but solely to the extent that any
+such patent is necessary to Utilize the software alone, or in
+combination with an operating system licensed under an approved Open
+Source license as listed by the Open Source Initiative at
+http://opensource.org/licenses. The patent license shall not apply to
+any other combinations which include this software. No hardware per
+se is licensed hereunder.
+
+DISCLAIMER. 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.
diff --git a/usr/src/uts/common/io/iwn/fw-iw/THIRDPARTYLICENSE.descrip b/usr/src/uts/common/io/iwn/fw-iw/THIRDPARTYLICENSE.descrip
new file mode 100644
index 0000000000..4bcbeb3edc
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/THIRDPARTYLICENSE.descrip
@@ -0,0 +1 @@
+Intel Wifi Link 4965 and 100/1000/2000/5000/6000 Series Firmware
diff --git a/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-100-5.ucode b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-100-5.ucode
new file mode 100644
index 0000000000..0e69a1a71c
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-100-5.ucode
Binary files differ
diff --git a/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-1000-3.ucode b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-1000-3.ucode
new file mode 100644
index 0000000000..f374446b03
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-1000-3.ucode
Binary files differ
diff --git a/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-105-6.ucode b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-105-6.ucode
new file mode 100644
index 0000000000..7d532e5c70
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-105-6.ucode
Binary files differ
diff --git a/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-135-6.ucode b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-135-6.ucode
new file mode 100644
index 0000000000..a0a32ede3c
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-135-6.ucode
Binary files differ
diff --git a/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-2000-6.ucode b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-2000-6.ucode
new file mode 100644
index 0000000000..3d58016cf9
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-2000-6.ucode
Binary files differ
diff --git a/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-2030-6.ucode b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-2030-6.ucode
new file mode 100644
index 0000000000..6ad76561b9
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-2030-6.ucode
Binary files differ
diff --git a/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-4965-2.ucode b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-4965-2.ucode
new file mode 100644
index 0000000000..dad4acfb60
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-4965-2.ucode
Binary files differ
diff --git a/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-5000-2.ucode b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-5000-2.ucode
new file mode 100644
index 0000000000..9e76e0df87
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-5000-2.ucode
Binary files differ
diff --git a/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-5150-2.ucode b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-5150-2.ucode
new file mode 100644
index 0000000000..04d658685b
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-5150-2.ucode
Binary files differ
diff --git a/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6000-4.ucode b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6000-4.ucode
new file mode 100644
index 0000000000..f9f25e40e1
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6000-4.ucode
Binary files differ
diff --git a/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6000g2a-5.ucode b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6000g2a-5.ucode
new file mode 100644
index 0000000000..24f7d146d0
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6000g2a-5.ucode
Binary files differ
diff --git a/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6000g2b-6.ucode b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6000g2b-6.ucode
new file mode 100644
index 0000000000..baaf9db773
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6000g2b-6.ucode
Binary files differ
diff --git a/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6050-5.ucode b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6050-5.ucode
new file mode 100644
index 0000000000..74a2085acf
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/fw-iw/iwlwifi-6050-5.ucode
Binary files differ
diff --git a/usr/src/uts/common/io/iwn/if_iwn.c b/usr/src/uts/common/io/iwn/if_iwn.c
new file mode 100644
index 0000000000..a4334cc19d
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/if_iwn.c
@@ -0,0 +1,7746 @@
+/* $NetBSD: if_iwn.c,v 1.78 2016/06/10 13:27:14 ozaki-r Exp $ */
+/* $OpenBSD: if_iwn.c,v 1.135 2014/09/10 07:22:09 dcoppa Exp $ */
+
+/*-
+ * Copyright (c) 2007-2010 Damien Bergamini <damien.bergamini@free.fr>
+ *
+ * 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.
+ */
+
+/*
+ * Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+ */
+
+/*
+ * Driver for Intel WiFi Link 4965 and 100/1000/2000/5000/6000 Series 802.11
+ * network adapters.
+ */
+
+/*
+ * TODO:
+ * - turn tunables into driver properties
+ */
+
+#undef IWN_HWCRYPTO /* XXX does not even compile yet */
+
+#include <sys/modctl.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/stat.h>
+
+#include <sys/param.h>
+#include <sys/sockio.h>
+#include <sys/proc.h>
+#include <sys/socket.h>
+#include <sys/systm.h>
+#include <sys/mutex.h>
+#include <sys/conf.h>
+
+#include <sys/pci.h>
+#include <sys/pcie.h>
+
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/in_var.h>
+#include <netinet/ip.h>
+
+#include <sys/dlpi.h>
+#include <sys/mac_provider.h>
+#include <sys/mac_wifi.h>
+#include <sys/net80211.h>
+#include <sys/firmload.h>
+#include <sys/queue.h>
+#include <sys/strsun.h>
+#include <sys/strsubr.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/kstat.h>
+
+#include <sys/sdt.h>
+
+#include "if_iwncompat.h"
+#include "if_iwnreg.h"
+#include "if_iwnvar.h"
+#include <inet/wifi_ioctl.h>
+
+#ifdef DEBUG
+#define IWN_DEBUG
+#endif
+
+/*
+ * regs access attributes
+ */
+static ddi_device_acc_attr_t iwn_reg_accattr = {
+ .devacc_attr_version = DDI_DEVICE_ATTR_V0,
+ .devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC,
+ .devacc_attr_dataorder = DDI_STRICTORDER_ACC,
+ .devacc_attr_access = DDI_DEFAULT_ACC
+};
+
+/*
+ * DMA access attributes for descriptor
+ */
+static ddi_device_acc_attr_t iwn_dma_descattr = {
+ .devacc_attr_version = DDI_DEVICE_ATTR_V0,
+ .devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC,
+ .devacc_attr_dataorder = DDI_STRICTORDER_ACC,
+ .devacc_attr_access = DDI_DEFAULT_ACC
+};
+
+/*
+ * DMA access attributes
+ */
+static ddi_device_acc_attr_t iwn_dma_accattr = {
+ .devacc_attr_version = DDI_DEVICE_ATTR_V0,
+ .devacc_attr_endian_flags = DDI_NEVERSWAP_ACC,
+ .devacc_attr_dataorder = DDI_STRICTORDER_ACC,
+ .devacc_attr_access = DDI_DEFAULT_ACC
+};
+
+
+/*
+ * Supported rates for 802.11a/b/g modes (in 500Kbps unit).
+ */
+static const struct ieee80211_rateset iwn_rateset_11a =
+ { 8, { 12, 18, 24, 36, 48, 72, 96, 108 } };
+
+static const struct ieee80211_rateset iwn_rateset_11b =
+ { 4, { 2, 4, 11, 22 } };
+
+static const struct ieee80211_rateset iwn_rateset_11g =
+ { 12, { 2, 4, 11, 22, 12, 18, 24, 36, 48, 72, 96, 108 } };
+
+static void iwn_kstat_create(struct iwn_softc *, const char *, size_t,
+ kstat_t **, void **);
+static void iwn_kstat_free(kstat_t *, void *, size_t);
+static void iwn_kstat_init(struct iwn_softc *);
+static void iwn_kstat_init_2000(struct iwn_softc *);
+static void iwn_kstat_init_4965(struct iwn_softc *);
+static void iwn_kstat_init_6000(struct iwn_softc *);
+static void iwn_intr_teardown(struct iwn_softc *);
+static int iwn_intr_add(struct iwn_softc *, int);
+static int iwn_intr_setup(struct iwn_softc *);
+static int iwn_attach(dev_info_t *, ddi_attach_cmd_t);
+static int iwn4965_attach(struct iwn_softc *);
+static int iwn5000_attach(struct iwn_softc *, uint16_t);
+static int iwn_detach(dev_info_t *, ddi_detach_cmd_t);
+static int iwn_quiesce(dev_info_t *);
+static int iwn_nic_lock(struct iwn_softc *);
+static int iwn_eeprom_lock(struct iwn_softc *);
+static int iwn_init_otprom(struct iwn_softc *);
+static int iwn_read_prom_data(struct iwn_softc *, uint32_t, void *, int);
+static int iwn_dma_contig_alloc(struct iwn_softc *, struct iwn_dma_info *,
+ uint_t, uint_t, void **, ddi_device_acc_attr_t *, uint_t);
+static void iwn_dma_contig_free(struct iwn_dma_info *);
+static int iwn_alloc_sched(struct iwn_softc *);
+static void iwn_free_sched(struct iwn_softc *);
+static int iwn_alloc_kw(struct iwn_softc *);
+static void iwn_free_kw(struct iwn_softc *);
+static int iwn_alloc_ict(struct iwn_softc *);
+static void iwn_free_ict(struct iwn_softc *);
+static int iwn_alloc_fwmem(struct iwn_softc *);
+static void iwn_free_fwmem(struct iwn_softc *);
+static int iwn_alloc_rx_ring(struct iwn_softc *, struct iwn_rx_ring *);
+static void iwn_reset_rx_ring(struct iwn_softc *, struct iwn_rx_ring *);
+static void iwn_free_rx_ring(struct iwn_softc *, struct iwn_rx_ring *);
+static int iwn_alloc_tx_ring(struct iwn_softc *, struct iwn_tx_ring *,
+ int);
+static void iwn_reset_tx_ring(struct iwn_softc *, struct iwn_tx_ring *);
+static void iwn_free_tx_ring(struct iwn_softc *, struct iwn_tx_ring *);
+static void iwn5000_ict_reset(struct iwn_softc *);
+static int iwn_read_eeprom(struct iwn_softc *);
+static void iwn4965_read_eeprom(struct iwn_softc *);
+
+#ifdef IWN_DEBUG
+static void iwn4965_print_power_group(struct iwn_softc *, int);
+#endif
+static void iwn5000_read_eeprom(struct iwn_softc *);
+static void iwn_read_eeprom_channels(struct iwn_softc *, int, uint32_t);
+static void iwn_read_eeprom_enhinfo(struct iwn_softc *);
+static struct ieee80211_node *iwn_node_alloc(ieee80211com_t *);
+static void iwn_node_free(ieee80211_node_t *);
+static void iwn_newassoc(struct ieee80211_node *, int);
+static int iwn_newstate(struct ieee80211com *, enum ieee80211_state, int);
+static void iwn_iter_func(void *, struct ieee80211_node *);
+static void iwn_calib_timeout(void *);
+static void iwn_rx_phy(struct iwn_softc *, struct iwn_rx_desc *,
+ struct iwn_rx_data *);
+static void iwn_rx_done(struct iwn_softc *, struct iwn_rx_desc *,
+ struct iwn_rx_data *);
+#ifndef IEEE80211_NO_HT
+static void iwn_rx_compressed_ba(struct iwn_softc *, struct iwn_rx_desc *,
+ struct iwn_rx_data *);
+#endif
+static void iwn5000_rx_calib_results(struct iwn_softc *,
+ struct iwn_rx_desc *, struct iwn_rx_data *);
+static void iwn_rx_statistics(struct iwn_softc *, struct iwn_rx_desc *,
+ struct iwn_rx_data *);
+static void iwn4965_tx_done(struct iwn_softc *, struct iwn_rx_desc *,
+ struct iwn_rx_data *);
+static void iwn5000_tx_done(struct iwn_softc *, struct iwn_rx_desc *,
+ struct iwn_rx_data *);
+static void iwn_tx_done(struct iwn_softc *, struct iwn_rx_desc *, int,
+ uint8_t);
+static void iwn_cmd_done(struct iwn_softc *, struct iwn_rx_desc *);
+static void iwn_notif_intr(struct iwn_softc *);
+static void iwn_wakeup_intr(struct iwn_softc *);
+static void iwn_fatal_intr(struct iwn_softc *);
+static uint_t iwn_intr(caddr_t, caddr_t);
+static void iwn4965_update_sched(struct iwn_softc *, int, int, uint8_t,
+ uint16_t);
+static void iwn5000_update_sched(struct iwn_softc *, int, int, uint8_t,
+ uint16_t);
+#ifdef notyet
+static void iwn5000_reset_sched(struct iwn_softc *, int, int);
+#endif
+static int iwn_send(ieee80211com_t *, mblk_t *, uint8_t);
+static void iwn_watchdog(void *);
+static int iwn_cmd(struct iwn_softc *, uint8_t, void *, int, int);
+static int iwn4965_add_node(struct iwn_softc *, struct iwn_node_info *,
+ int);
+static int iwn5000_add_node(struct iwn_softc *, struct iwn_node_info *,
+ int);
+static int iwn_set_link_quality(struct iwn_softc *,
+ struct ieee80211_node *);
+static int iwn_add_broadcast_node(struct iwn_softc *, int);
+static void iwn_set_led(struct iwn_softc *, uint8_t, uint8_t, uint8_t);
+static int iwn_set_critical_temp(struct iwn_softc *);
+static int iwn_set_timing(struct iwn_softc *, struct ieee80211_node *);
+static void iwn4965_power_calibration(struct iwn_softc *, int);
+static int iwn4965_set_txpower(struct iwn_softc *, int);
+static int iwn5000_set_txpower(struct iwn_softc *, int);
+static int iwn4965_get_rssi(const struct iwn_rx_stat *);
+static int iwn5000_get_rssi(const struct iwn_rx_stat *);
+static int iwn_get_noise(const struct iwn_rx_general_stats *);
+static int iwn4965_get_temperature(struct iwn_softc *);
+static int iwn5000_get_temperature(struct iwn_softc *);
+static int iwn_init_sensitivity(struct iwn_softc *);
+static void iwn_collect_noise(struct iwn_softc *,
+ const struct iwn_rx_general_stats *);
+static int iwn4965_init_gains(struct iwn_softc *);
+static int iwn5000_init_gains(struct iwn_softc *);
+static int iwn4965_set_gains(struct iwn_softc *);
+static int iwn5000_set_gains(struct iwn_softc *);
+static void iwn_tune_sensitivity(struct iwn_softc *,
+ const struct iwn_rx_stats *);
+static int iwn_send_sensitivity(struct iwn_softc *);
+static int iwn_set_pslevel(struct iwn_softc *, int, int, int);
+static int iwn5000_runtime_calib(struct iwn_softc *);
+
+static int iwn_config_bt_coex_bluetooth(struct iwn_softc *);
+static int iwn_config_bt_coex_prio_table(struct iwn_softc *);
+static int iwn_config_bt_coex_adv1(struct iwn_softc *);
+static int iwn_config_bt_coex_adv2(struct iwn_softc *);
+
+static int iwn_config(struct iwn_softc *);
+static uint16_t iwn_get_active_dwell_time(struct iwn_softc *, uint16_t,
+ uint8_t);
+static uint16_t iwn_limit_dwell(struct iwn_softc *, uint16_t);
+static uint16_t iwn_get_passive_dwell_time(struct iwn_softc *, uint16_t);
+static int iwn_scan(struct iwn_softc *, uint16_t);
+static int iwn_auth(struct iwn_softc *);
+static int iwn_run(struct iwn_softc *);
+#ifdef IWN_HWCRYPTO
+static int iwn_set_key(struct ieee80211com *, struct ieee80211_node *,
+ struct ieee80211_key *);
+static void iwn_delete_key(struct ieee80211com *, struct ieee80211_node *,
+ struct ieee80211_key *);
+#endif
+static int iwn_wme_update(struct ieee80211com *);
+#ifndef IEEE80211_NO_HT
+static int iwn_ampdu_rx_start(struct ieee80211com *,
+ struct ieee80211_node *, uint8_t);
+static void iwn_ampdu_rx_stop(struct ieee80211com *,
+ struct ieee80211_node *, uint8_t);
+static int iwn_ampdu_tx_start(struct ieee80211com *,
+ struct ieee80211_node *, uint8_t);
+static void iwn_ampdu_tx_stop(struct ieee80211com *,
+ struct ieee80211_node *, uint8_t);
+static void iwn4965_ampdu_tx_start(struct iwn_softc *,
+ struct ieee80211_node *, uint8_t, uint16_t);
+static void iwn4965_ampdu_tx_stop(struct iwn_softc *,
+ uint8_t, uint16_t);
+static void iwn5000_ampdu_tx_start(struct iwn_softc *,
+ struct ieee80211_node *, uint8_t, uint16_t);
+static void iwn5000_ampdu_tx_stop(struct iwn_softc *,
+ uint8_t, uint16_t);
+#endif
+static int iwn5000_query_calibration(struct iwn_softc *);
+static int iwn5000_send_calibration(struct iwn_softc *);
+static int iwn5000_send_wimax_coex(struct iwn_softc *);
+static int iwn6000_temp_offset_calib(struct iwn_softc *);
+static int iwn2000_temp_offset_calib(struct iwn_softc *);
+static int iwn4965_post_alive(struct iwn_softc *);
+static int iwn5000_post_alive(struct iwn_softc *);
+static int iwn4965_load_bootcode(struct iwn_softc *, const uint8_t *,
+ int);
+static int iwn4965_load_firmware(struct iwn_softc *);
+static int iwn5000_load_firmware_section(struct iwn_softc *, uint32_t,
+ const uint8_t *, int);
+static int iwn5000_load_firmware(struct iwn_softc *);
+static int iwn_read_firmware_leg(struct iwn_softc *,
+ struct iwn_fw_info *);
+static int iwn_read_firmware_tlv(struct iwn_softc *,
+ struct iwn_fw_info *, uint16_t);
+static int iwn_read_firmware(struct iwn_softc *);
+static int iwn_clock_wait(struct iwn_softc *);
+static int iwn_apm_init(struct iwn_softc *);
+static void iwn_apm_stop_master(struct iwn_softc *);
+static void iwn_apm_stop(struct iwn_softc *);
+static int iwn4965_nic_config(struct iwn_softc *);
+static int iwn5000_nic_config(struct iwn_softc *);
+static int iwn_hw_prepare(struct iwn_softc *);
+static int iwn_hw_init(struct iwn_softc *);
+static void iwn_hw_stop(struct iwn_softc *, boolean_t);
+static int iwn_init(struct iwn_softc *);
+static void iwn_abort_scan(void *);
+static void iwn_periodic(void *);
+static int iwn_fast_recover(struct iwn_softc *);
+
+static uint8_t *ieee80211_add_ssid(uint8_t *, const uint8_t *, uint32_t);
+static uint8_t *ieee80211_add_rates(uint8_t *,
+ const struct ieee80211_rateset *);
+static uint8_t *ieee80211_add_xrates(uint8_t *,
+ const struct ieee80211_rateset *);
+
+static void iwn_fix_channel(struct iwn_softc *, mblk_t *,
+ struct iwn_rx_stat *);
+
+#ifdef IWN_DEBUG
+
+#define IWN_DBG(...) iwn_dbg("?" __VA_ARGS__)
+
+static int iwn_dbg_print = 0;
+
+static void
+iwn_dbg(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (iwn_dbg_print != 0) {
+ va_start(ap, fmt);
+ vcmn_err(CE_CONT, fmt, ap);
+ va_end(ap);
+ }
+}
+
+#else
+#define IWN_DBG(...)
+#endif
+
+/*
+ * tunables
+ */
+
+/*
+ * enable 5GHz scanning
+ */
+int iwn_enable_5ghz = 1;
+
+/*
+ * If more than 50 consecutive beacons are missed,
+ * we've probably lost our connection.
+ * If more than 5 consecutive beacons are missed,
+ * reinitialize the sensitivity state machine.
+ */
+int iwn_beacons_missed_disconnect = 50;
+int iwn_beacons_missed_sensitivity = 5;
+
+/*
+ * iwn_periodic interval, in units of msec
+ */
+int iwn_periodic_interval = 100;
+
+/*
+ * scan timeout in sec
+ */
+int iwn_scan_timeout = 20;
+
+static ether_addr_t etherbroadcastaddr = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+static void *iwn_state = NULL;
+
+/*
+ * Mac Call Back entries
+ */
+static int iwn_m_stat(void *, uint_t, uint64_t *);
+static int iwn_m_start(void *);
+static void iwn_m_stop(void *);
+static int iwn_m_unicst(void *, const uint8_t *);
+static int iwn_m_multicst(void *, boolean_t, const uint8_t *);
+static int iwn_m_promisc(void *, boolean_t);
+static mblk_t *iwn_m_tx(void *, mblk_t *);
+static void iwn_m_ioctl(void *, queue_t *, mblk_t *);
+static int iwn_m_setprop(void *, const char *, mac_prop_id_t, uint_t,
+ const void *);
+static int iwn_m_getprop(void *, const char *, mac_prop_id_t, uint_t,
+ void *);
+static void iwn_m_propinfo(void *, const char *, mac_prop_id_t,
+ mac_prop_info_handle_t);
+
+mac_callbacks_t iwn_m_callbacks = {
+ .mc_callbacks = MC_IOCTL | MC_SETPROP | MC_GETPROP | MC_PROPINFO,
+ .mc_getstat = iwn_m_stat,
+ .mc_start = iwn_m_start,
+ .mc_stop = iwn_m_stop,
+ .mc_setpromisc = iwn_m_promisc,
+ .mc_multicst = iwn_m_multicst,
+ .mc_unicst = iwn_m_unicst,
+ .mc_tx = iwn_m_tx,
+ .mc_reserved = NULL,
+ .mc_ioctl = iwn_m_ioctl,
+ .mc_getcapab = NULL,
+ .mc_open = NULL,
+ .mc_close = NULL,
+ .mc_setprop = iwn_m_setprop,
+ .mc_getprop = iwn_m_getprop,
+ .mc_propinfo = iwn_m_propinfo
+};
+
+static inline uint32_t
+iwn_read(struct iwn_softc *sc, int reg)
+{
+ /*LINTED: E_PTR_BAD_CAST_ALIGN*/
+ return (ddi_get32(sc->sc_regh, (uint32_t *)(sc->sc_base + reg)));
+}
+
+static inline void
+iwn_write(struct iwn_softc *sc, int reg, uint32_t val)
+{
+ /*LINTED: E_PTR_BAD_CAST_ALIGN*/
+ ddi_put32(sc->sc_regh, (uint32_t *)(sc->sc_base + reg), val);
+}
+
+static inline void
+iwn_write_1(struct iwn_softc *sc, int reg, uint8_t val)
+{
+ ddi_put8(sc->sc_regh, (uint8_t *)(sc->sc_base + reg), val);
+}
+
+static void
+iwn_kstat_create(struct iwn_softc *sc, const char *name, size_t size,
+ kstat_t **ks, void **data)
+{
+ *ks = kstat_create(ddi_driver_name(sc->sc_dip),
+ ddi_get_instance(sc->sc_dip), name, "misc", KSTAT_TYPE_NAMED,
+ size / sizeof (kstat_named_t), 0);
+ if (*ks == NULL)
+ *data = kmem_zalloc(size, KM_SLEEP);
+ else
+ *data = (*ks)->ks_data;
+}
+
+static void
+iwn_kstat_free(kstat_t *ks, void *data, size_t size)
+{
+ if (ks != NULL)
+ kstat_delete(ks);
+ else if (data != NULL)
+ kmem_free(data, size);
+}
+
+static void
+iwn_kstat_init(struct iwn_softc *sc)
+{
+ if (sc->sc_ks_misc != NULL)
+ sc->sc_ks_misc->ks_lock = &sc->sc_mtx;
+ if (sc->sc_ks_ant != NULL)
+ sc->sc_ks_ant->ks_lock = &sc->sc_mtx;
+ if (sc->sc_ks_sens != NULL)
+ sc->sc_ks_sens->ks_lock = &sc->sc_mtx;
+ if (sc->sc_ks_timing != NULL)
+ sc->sc_ks_timing->ks_lock = &sc->sc_mtx;
+ if (sc->sc_ks_edca != NULL)
+ sc->sc_ks_edca->ks_lock = &sc->sc_mtx;
+
+ kstat_named_init(&sc->sc_misc->temp,
+ "temperature", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_misc->crit_temp,
+ "critical temperature", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_misc->pslevel,
+ "power saving level", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_misc->noise,
+ "noise", KSTAT_DATA_LONG);
+
+
+ kstat_named_init(&sc->sc_ant->tx_ant,
+ "TX mask", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_ant->rx_ant,
+ "RX mask", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_ant->conn_ant,
+ "connected mask", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_ant->gain[0],
+ "gain A", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_ant->gain[1],
+ "gain B", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_ant->gain[2],
+ "gain C", KSTAT_DATA_ULONG);
+
+ kstat_named_init(&sc->sc_sens->ofdm_x1,
+ "OFDM X1", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_sens->ofdm_mrc_x1,
+ "OFDM MRC X1", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_sens->ofdm_x4,
+ "OFDM X4", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_sens->ofdm_mrc_x4,
+ "OFDM MRC X4", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_sens->cck_x4,
+ "CCK X4", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_sens->cck_mrc_x4,
+ "CCK MRC X4", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_sens->energy_cck,
+ "energy CCK", KSTAT_DATA_ULONG);
+
+ kstat_named_init(&sc->sc_timing->bintval,
+ "bintval", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_timing->tstamp,
+ "timestamp", KSTAT_DATA_ULONGLONG);
+ kstat_named_init(&sc->sc_timing->init,
+ "init", KSTAT_DATA_ULONG);
+
+ kstat_named_init(&sc->sc_edca->ac[0].cwmin,
+ "background cwmin", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[0].cwmax,
+ "background cwmax", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[0].aifsn,
+ "background aifsn", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[0].txop,
+ "background txop", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[1].cwmin,
+ "best effort cwmin", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[1].cwmax,
+ "best effort cwmax", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[1].aifsn,
+ "best effort aifsn", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[1].txop,
+ "best effort txop", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[2].cwmin,
+ "video cwmin", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[2].cwmax,
+ "video cwmax", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[2].aifsn,
+ "video aifsn", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[2].txop,
+ "video txop", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[3].cwmin,
+ "voice cwmin", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[3].cwmax,
+ "voice cwmax", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[3].aifsn,
+ "voice aifsn", KSTAT_DATA_ULONG);
+ kstat_named_init(&sc->sc_edca->ac[3].txop,
+ "voice txop", KSTAT_DATA_ULONG);
+}
+
+static void
+iwn_kstat_init_2000(struct iwn_softc *sc)
+{
+ if (sc->sc_ks_toff != NULL)
+ sc->sc_ks_toff->ks_lock = &sc->sc_mtx;
+
+ kstat_named_init(&sc->sc_toff.t2000->toff_lo,
+ "temperature offset low", KSTAT_DATA_LONG);
+ kstat_named_init(&sc->sc_toff.t2000->toff_hi,
+ "temperature offset high", KSTAT_DATA_LONG);
+ kstat_named_init(&sc->sc_toff.t2000->volt,
+ "reference voltage", KSTAT_DATA_LONG);
+}
+
+static void
+iwn_kstat_init_4965(struct iwn_softc *sc)
+{
+ int i, r;
+
+ if (sc->sc_ks_txpower != NULL)
+ sc->sc_ks_txpower->ks_lock = &sc->sc_mtx;
+
+ kstat_named_init(&sc->sc_txpower->vdiff,
+ "voltage comp", KSTAT_DATA_LONG);
+ kstat_named_init(&sc->sc_txpower->chan,
+ "channel", KSTAT_DATA_LONG);
+ kstat_named_init(&sc->sc_txpower->group,
+ "attenuation group", KSTAT_DATA_LONG);
+ kstat_named_init(&sc->sc_txpower->subband,
+ "sub-band", KSTAT_DATA_LONG);
+ for (i = 0; i != 2; i++) {
+ char tmp[KSTAT_STRLEN];
+
+ (void) snprintf(tmp, KSTAT_STRLEN - 1, "Ant %d power", i);
+ kstat_named_init(&sc->sc_txpower->txchain[i].power,
+ tmp, KSTAT_DATA_LONG);
+
+ (void) snprintf(tmp, KSTAT_STRLEN - 1, "Ant %d gain", i);
+ kstat_named_init(&sc->sc_txpower->txchain[i].gain,
+ tmp, KSTAT_DATA_LONG);
+
+ (void) snprintf(tmp, KSTAT_STRLEN - 1, "Ant %d temperature", i);
+ kstat_named_init(&sc->sc_txpower->txchain[i].temp,
+ tmp, KSTAT_DATA_LONG);
+
+ (void) snprintf(tmp, KSTAT_STRLEN - 1,
+ "Ant %d temperature compensation", i);
+ kstat_named_init(&sc->sc_txpower->txchain[i].tcomp,
+ tmp, KSTAT_DATA_LONG);
+
+ for (r = 0; r != IWN_RIDX_MAX; r++) {
+ (void) snprintf(tmp, KSTAT_STRLEN - 1,
+ "Ant %d Rate %d RF gain", i, r);
+ kstat_named_init(
+ &sc->sc_txpower->txchain[i].rate[r].rf_gain,
+ tmp, KSTAT_DATA_LONG);
+
+ (void) snprintf(tmp, KSTAT_STRLEN - 1,
+ "Ant %d Rate %d DSP gain", i, r);
+ kstat_named_init(
+ &sc->sc_txpower->txchain[0].rate[0].dsp_gain,
+ tmp, KSTAT_DATA_LONG);
+ }
+ }
+}
+
+static void
+iwn_kstat_init_6000(struct iwn_softc *sc)
+{
+ if (sc->sc_ks_toff != NULL)
+ sc->sc_ks_toff->ks_lock = &sc->sc_mtx;
+
+ kstat_named_init(&sc->sc_toff.t6000->toff,
+ "temperature offset", KSTAT_DATA_LONG);
+}
+
+static void
+iwn_intr_teardown(struct iwn_softc *sc)
+{
+ if (sc->sc_intr_htable != NULL) {
+ if ((sc->sc_intr_cap & DDI_INTR_FLAG_BLOCK) != 0) {
+ (void) ddi_intr_block_disable(sc->sc_intr_htable,
+ sc->sc_intr_count);
+ } else {
+ (void) ddi_intr_disable(sc->sc_intr_htable[0]);
+ }
+ (void) ddi_intr_remove_handler(sc->sc_intr_htable[0]);
+ (void) ddi_intr_free(sc->sc_intr_htable[0]);
+ sc->sc_intr_htable[0] = NULL;
+
+ kmem_free(sc->sc_intr_htable, sc->sc_intr_size);
+ sc->sc_intr_size = 0;
+ sc->sc_intr_htable = NULL;
+ }
+}
+
+static int
+iwn_intr_add(struct iwn_softc *sc, int intr_type)
+{
+ int ni, na;
+ int ret;
+ char *func;
+
+ if (ddi_intr_get_nintrs(sc->sc_dip, intr_type, &ni) != DDI_SUCCESS)
+ return (DDI_FAILURE);
+
+
+ if (ddi_intr_get_navail(sc->sc_dip, intr_type, &na) != DDI_SUCCESS)
+ return (DDI_FAILURE);
+
+ sc->sc_intr_size = sizeof (ddi_intr_handle_t);
+ sc->sc_intr_htable = kmem_zalloc(sc->sc_intr_size, KM_SLEEP);
+
+ ret = ddi_intr_alloc(sc->sc_dip, sc->sc_intr_htable, intr_type, 0, 1,
+ &sc->sc_intr_count, DDI_INTR_ALLOC_STRICT);
+ if (ret != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN, "!ddi_intr_alloc() failed");
+ return (DDI_FAILURE);
+ }
+
+ ret = ddi_intr_get_pri(sc->sc_intr_htable[0], &sc->sc_intr_pri);
+ if (ret != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN, "!ddi_intr_get_pri() failed");
+ return (DDI_FAILURE);
+ }
+
+ ret = ddi_intr_add_handler(sc->sc_intr_htable[0], iwn_intr, (caddr_t)sc,
+ NULL);
+ if (ret != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN, "!ddi_intr_add_handler() failed");
+ return (DDI_FAILURE);
+ }
+
+ ret = ddi_intr_get_cap(sc->sc_intr_htable[0], &sc->sc_intr_cap);
+ if (ret != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN, "!ddi_intr_get_cap() failed");
+ return (DDI_FAILURE);
+ }
+
+ if ((sc->sc_intr_cap & DDI_INTR_FLAG_BLOCK) != 0) {
+ ret = ddi_intr_block_enable(sc->sc_intr_htable,
+ sc->sc_intr_count);
+ func = "ddi_intr_enable_block";
+ } else {
+ ret = ddi_intr_enable(sc->sc_intr_htable[0]);
+ func = "ddi_intr_enable";
+ }
+
+ if (ret != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN, "!%s() failed", func);
+ return (DDI_FAILURE);
+ }
+
+ return (DDI_SUCCESS);
+}
+
+static int
+iwn_intr_setup(struct iwn_softc *sc)
+{
+ int intr_type;
+ int ret;
+
+ ret = ddi_intr_get_supported_types(sc->sc_dip, &intr_type);
+ if (ret != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!ddi_intr_get_supported_types() failed");
+ return (DDI_FAILURE);
+ }
+
+ if ((intr_type & DDI_INTR_TYPE_MSIX)) {
+ if (iwn_intr_add(sc, DDI_INTR_TYPE_MSIX) == DDI_SUCCESS)
+ return (DDI_SUCCESS);
+ iwn_intr_teardown(sc);
+ }
+
+ if ((intr_type & DDI_INTR_TYPE_MSI)) {
+ if (iwn_intr_add(sc, DDI_INTR_TYPE_MSI) == DDI_SUCCESS)
+ return (DDI_SUCCESS);
+ iwn_intr_teardown(sc);
+ }
+
+ if ((intr_type & DDI_INTR_TYPE_FIXED)) {
+ if (iwn_intr_add(sc, DDI_INTR_TYPE_FIXED) == DDI_SUCCESS)
+ return (DDI_SUCCESS);
+ iwn_intr_teardown(sc);
+ }
+
+ dev_err(sc->sc_dip, CE_WARN, "!iwn_intr_setup() failed");
+ return (DDI_FAILURE);
+}
+
+static int
+iwn_pci_get_capability(ddi_acc_handle_t pcih, int cap, int *cap_off)
+{
+ uint8_t ptr;
+ uint8_t val;
+
+ for (ptr = pci_config_get8(pcih, PCI_CONF_CAP_PTR);
+ ptr != 0 && ptr != 0xff;
+ ptr = pci_config_get8(pcih, ptr + PCI_CAP_NEXT_PTR)) {
+ val = pci_config_get8(pcih, ptr + PCIE_CAP_ID);
+ if (val == 0xff)
+ return (DDI_FAILURE);
+
+ if (cap != val)
+ continue;
+
+ *cap_off = ptr;
+ return (DDI_SUCCESS);
+ }
+
+ return (DDI_FAILURE);
+}
+
+static int
+iwn_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ int instance;
+
+ struct iwn_softc *sc;
+ struct ieee80211com *ic;
+ char strbuf[32];
+ wifi_data_t wd = { 0 };
+ mac_register_t *macp;
+ uint32_t reg;
+ int i, error;
+
+ switch (cmd) {
+ case DDI_ATTACH:
+ break;
+
+ case DDI_RESUME:
+ instance = ddi_get_instance(dip);
+ sc = ddi_get_soft_state(iwn_state,
+ instance);
+ ASSERT(sc != NULL);
+
+ if (sc->sc_flags & IWN_FLAG_RUNNING) {
+ (void) iwn_init(sc);
+ }
+
+ sc->sc_flags &= ~IWN_FLAG_SUSPEND;
+
+ return (DDI_SUCCESS);
+ default:
+ return (DDI_FAILURE);
+ }
+
+ instance = ddi_get_instance(dip);
+
+ if (ddi_soft_state_zalloc(iwn_state, instance) != DDI_SUCCESS) {
+ dev_err(dip, CE_WARN, "!ddi_soft_state_zalloc() failed");
+ return (DDI_FAILURE);
+ }
+
+ sc = ddi_get_soft_state(iwn_state, instance);
+ ddi_set_driver_private(dip, (caddr_t)sc);
+
+ ic = &sc->sc_ic;
+
+ sc->sc_dip = dip;
+
+ iwn_kstat_create(sc, "hw_state", sizeof (struct iwn_ks_misc),
+ &sc->sc_ks_misc, (void **)&sc->sc_misc);
+ iwn_kstat_create(sc, "antennas", sizeof (struct iwn_ks_ant),
+ &sc->sc_ks_ant, (void **)&sc->sc_ant);
+ iwn_kstat_create(sc, "sensitivity", sizeof (struct iwn_ks_sens),
+ &sc->sc_ks_sens, (void **)&sc->sc_sens);
+ iwn_kstat_create(sc, "timing", sizeof (struct iwn_ks_timing),
+ &sc->sc_ks_timing, (void **)&sc->sc_timing);
+ iwn_kstat_create(sc, "edca", sizeof (struct iwn_ks_edca),
+ &sc->sc_ks_edca, (void **)&sc->sc_edca);
+
+ if (pci_config_setup(dip, &sc->sc_pcih) != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN, "!pci_config_setup() failed");
+ goto fail_pci_config;
+ }
+
+ /*
+ * Get the offset of the PCI Express Capability Structure in PCI
+ * Configuration Space.
+ */
+ error = iwn_pci_get_capability(sc->sc_pcih, PCI_CAP_ID_PCI_E,
+ &sc->sc_cap_off);
+ if (error != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!PCIe capability structure not found!");
+ goto fail_pci_capab;
+ }
+
+ /* Clear device-specific "PCI retry timeout" register (41h). */
+ reg = pci_config_get8(sc->sc_pcih, 0x41);
+ if (reg)
+ pci_config_put8(sc->sc_pcih, 0x41, 0);
+
+ error = ddi_regs_map_setup(dip, 1, &sc->sc_base, 0, 0, &iwn_reg_accattr,
+ &sc->sc_regh);
+ if (error != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN, "!ddi_regs_map_setup() failed");
+ goto fail_regs_map;
+ }
+
+ /* Clear pending interrupts. */
+ IWN_WRITE(sc, IWN_INT, 0xffffffff);
+
+ /* Disable all interrupts. */
+ IWN_WRITE(sc, IWN_INT_MASK, 0);
+
+ /* Install interrupt handler. */
+ if (iwn_intr_setup(sc) != DDI_SUCCESS)
+ goto fail_intr;
+
+ mutex_init(&sc->sc_mtx, NULL, MUTEX_DRIVER,
+ DDI_INTR_PRI(sc->sc_intr_pri));
+ mutex_init(&sc->sc_tx_mtx, NULL, MUTEX_DRIVER,
+ DDI_INTR_PRI(sc->sc_intr_pri));
+ mutex_init(&sc->sc_mt_mtx, NULL, MUTEX_DRIVER,
+ DDI_INTR_PRI(sc->sc_intr_pri));
+
+ cv_init(&sc->sc_cmd_cv, NULL, CV_DRIVER, NULL);
+ cv_init(&sc->sc_scan_cv, NULL, CV_DRIVER, NULL);
+ cv_init(&sc->sc_fhdma_cv, NULL, CV_DRIVER, NULL);
+ cv_init(&sc->sc_alive_cv, NULL, CV_DRIVER, NULL);
+ cv_init(&sc->sc_calib_cv, NULL, CV_DRIVER, NULL);
+
+ iwn_kstat_init(sc);
+
+ /* Read hardware revision and attach. */
+ sc->hw_type =
+ (IWN_READ(sc, IWN_HW_REV) & IWN_HW_REV_TYPE_MASK)
+ >> IWN_HW_REV_TYPE_SHIFT;
+ if (sc->hw_type == IWN_HW_REV_TYPE_4965)
+ error = iwn4965_attach(sc);
+ else
+ error = iwn5000_attach(sc, sc->sc_devid);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN, "!could not attach device");
+ goto fail_hw;
+ }
+
+ if ((error = iwn_hw_prepare(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN, "!hardware not ready");
+ goto fail_hw;
+ }
+
+ /* Read MAC address, channels, etc from EEPROM. */
+ if ((error = iwn_read_eeprom(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN, "!could not read EEPROM");
+ goto fail_hw;
+ }
+
+ /* Allocate DMA memory for firmware transfers. */
+ if ((error = iwn_alloc_fwmem(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not allocate memory for firmware");
+ goto fail_fwmem;
+ }
+
+ /* Allocate "Keep Warm" page. */
+ if ((error = iwn_alloc_kw(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not allocate keep warm page");
+ goto fail_kw;
+ }
+
+ /* Allocate ICT table for 5000 Series. */
+ if (sc->hw_type != IWN_HW_REV_TYPE_4965 &&
+ (error = iwn_alloc_ict(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN, "!could not allocate ICT table");
+ goto fail_ict;
+ }
+
+ /* Allocate TX scheduler "rings". */
+ if ((error = iwn_alloc_sched(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not allocate TX scheduler rings");
+ goto fail_sched;
+ }
+
+ /* Allocate TX rings (16 on 4965AGN, 20 on >=5000). */
+ for (i = 0; i < sc->ntxqs; i++) {
+ if ((error = iwn_alloc_tx_ring(sc, &sc->txq[i], i)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not allocate TX ring %d", i);
+ while (--i >= 0)
+ iwn_free_tx_ring(sc, &sc->txq[i]);
+ goto fail_txring;
+ }
+ }
+
+ /* Allocate RX ring. */
+ if ((error = iwn_alloc_rx_ring(sc, &sc->rxq)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN, "!could not allocate RX ring");
+ goto fail_rxring;
+ }
+
+ /* Clear pending interrupts. */
+ IWN_WRITE(sc, IWN_INT, 0xffffffff);
+
+ /* Count the number of available chains. */
+ sc->ntxchains =
+ ((sc->txchainmask >> 2) & 1) +
+ ((sc->txchainmask >> 1) & 1) +
+ ((sc->txchainmask >> 0) & 1);
+ sc->nrxchains =
+ ((sc->rxchainmask >> 2) & 1) +
+ ((sc->rxchainmask >> 1) & 1) +
+ ((sc->rxchainmask >> 0) & 1);
+ dev_err(sc->sc_dip, CE_CONT, "!MIMO %dT%dR, %s, address %s",
+ sc->ntxchains, sc->nrxchains, sc->eeprom_domain,
+ ieee80211_macaddr_sprintf(ic->ic_macaddr));
+
+ sc->sc_ant->tx_ant.value.ul = sc->txchainmask;
+ sc->sc_ant->rx_ant.value.ul = sc->rxchainmask;
+
+ ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */
+ ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */
+ ic->ic_state = IEEE80211_S_INIT;
+
+ /* Set device capabilities. */
+ /* XXX OpenBSD has IEEE80211_C_WEP, IEEE80211_C_RSN,
+ * and IEEE80211_C_PMGT too. */
+ ic->ic_caps =
+ IEEE80211_C_IBSS | /* IBSS mode support */
+ IEEE80211_C_WPA | /* 802.11i */
+ IEEE80211_C_MONITOR | /* monitor mode supported */
+ IEEE80211_C_TXPMGT | /* tx power management */
+ IEEE80211_C_SHSLOT | /* short slot time supported */
+ IEEE80211_C_SHPREAMBLE | /* short preamble supported */
+ IEEE80211_C_WME; /* 802.11e */
+
+#ifndef IEEE80211_NO_HT
+ if (sc->sc_flags & IWN_FLAG_HAS_11N) {
+ /* Set HT capabilities. */
+ ic->ic_htcaps =
+#if IWN_RBUF_SIZE == 8192
+ IEEE80211_HTCAP_AMSDU7935 |
+#endif
+ IEEE80211_HTCAP_CBW20_40 |
+ IEEE80211_HTCAP_SGI20 |
+ IEEE80211_HTCAP_SGI40;
+ if (sc->hw_type != IWN_HW_REV_TYPE_4965)
+ ic->ic_htcaps |= IEEE80211_HTCAP_GF;
+ if (sc->hw_type == IWN_HW_REV_TYPE_6050)
+ ic->ic_htcaps |= IEEE80211_HTCAP_SMPS_DYN;
+ else
+ ic->ic_htcaps |= IEEE80211_HTCAP_SMPS_DIS;
+ }
+#endif /* !IEEE80211_NO_HT */
+
+ /* Set supported legacy rates. */
+ ic->ic_sup_rates[IEEE80211_MODE_11B] = iwn_rateset_11b;
+ ic->ic_sup_rates[IEEE80211_MODE_11G] = iwn_rateset_11g;
+ if (sc->sc_flags & IWN_FLAG_HAS_5GHZ) {
+ ic->ic_sup_rates[IEEE80211_MODE_11A] = iwn_rateset_11a;
+ }
+#ifndef IEEE80211_NO_HT
+ if (sc->sc_flags & IWN_FLAG_HAS_11N) {
+ /* Set supported HT rates. */
+ ic->ic_sup_mcs[0] = 0xff; /* MCS 0-7 */
+ if (sc->nrxchains > 1)
+ ic->ic_sup_mcs[1] = 0xff; /* MCS 7-15 */
+ if (sc->nrxchains > 2)
+ ic->ic_sup_mcs[2] = 0xff; /* MCS 16-23 */
+ }
+#endif
+
+ /* IBSS channel undefined for now. */
+ ic->ic_ibss_chan = &ic->ic_sup_channels[0];
+
+ ic->ic_node_newassoc = iwn_newassoc;
+ ic->ic_xmit = iwn_send;
+#ifdef IWN_HWCRYPTO
+ ic->ic_crypto.cs_key_set = iwn_set_key;
+ ic->ic_crypto.cs_key_delete = iwn_delete_key;
+#endif
+ ic->ic_wme.wme_update = iwn_wme_update;
+#ifndef IEEE80211_NO_HT
+ ic->ic_ampdu_rx_start = iwn_ampdu_rx_start;
+ ic->ic_ampdu_rx_stop = iwn_ampdu_rx_stop;
+ ic->ic_ampdu_tx_start = iwn_ampdu_tx_start;
+ ic->ic_ampdu_tx_stop = iwn_ampdu_tx_stop;
+#endif
+ /*
+ * attach to 802.11 module
+ */
+ ieee80211_attach(ic);
+
+ ieee80211_register_door(ic, ddi_driver_name(dip), ddi_get_instance(dip));
+
+ /* Override 802.11 state transition machine. */
+ sc->sc_newstate = ic->ic_newstate;
+ ic->ic_newstate = iwn_newstate;
+ ic->ic_watchdog = iwn_watchdog;
+
+ ic->ic_node_alloc = iwn_node_alloc;
+ ic->ic_node_free = iwn_node_free;
+
+ ieee80211_media_init(ic);
+
+ /*
+ * initialize default tx key
+ */
+ ic->ic_def_txkey = 0;
+
+ sc->amrr.amrr_min_success_threshold = 1;
+ sc->amrr.amrr_max_success_threshold = 15;
+
+ /*
+ * Initialize pointer to device specific functions
+ */
+ wd.wd_secalloc = WIFI_SEC_NONE;
+ wd.wd_opmode = ic->ic_opmode;
+ IEEE80211_ADDR_COPY(wd.wd_bssid, ic->ic_macaddr);
+
+ /*
+ * create relation to GLD
+ */
+ macp = mac_alloc(MAC_VERSION);
+ if (NULL == macp) {
+ dev_err(sc->sc_dip, CE_WARN, "!mac_alloc() failed");
+ goto fail_mac_alloc;
+ }
+
+ macp->m_type_ident = MAC_PLUGIN_IDENT_WIFI;
+ macp->m_driver = sc;
+ macp->m_dip = dip;
+ macp->m_src_addr = ic->ic_macaddr;
+ macp->m_callbacks = &iwn_m_callbacks;
+ macp->m_min_sdu = 0;
+ macp->m_max_sdu = IEEE80211_MTU;
+ macp->m_pdata = &wd;
+ macp->m_pdata_size = sizeof (wd);
+
+ /*
+ * Register the macp to mac
+ */
+ error = mac_register(macp, &ic->ic_mach);
+ mac_free(macp);
+ if (error != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN, "!mac_register() failed");
+ goto fail_mac_alloc;
+ }
+
+ /*
+ * Create minor node of type DDI_NT_NET_WIFI
+ */
+ (void) snprintf(strbuf, sizeof (strbuf), "iwn%d", instance);
+ error = ddi_create_minor_node(dip, strbuf, S_IFCHR,
+ instance + 1, DDI_NT_NET_WIFI, 0);
+ if (error != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN, "!ddi_create_minor_node() failed");
+ goto fail_minor;
+ }
+
+ /*
+ * Notify link is down now
+ */
+ mac_link_update(ic->ic_mach, LINK_STATE_DOWN);
+
+ sc->sc_periodic = ddi_periodic_add(iwn_periodic, sc,
+ iwn_periodic_interval * MICROSEC, 0);
+
+ if (sc->sc_ks_misc)
+ kstat_install(sc->sc_ks_misc);
+ if (sc->sc_ks_ant)
+ kstat_install(sc->sc_ks_ant);
+ if (sc->sc_ks_sens)
+ kstat_install(sc->sc_ks_sens);
+ if (sc->sc_ks_timing)
+ kstat_install(sc->sc_ks_timing);
+ if (sc->sc_ks_edca)
+ kstat_install(sc->sc_ks_edca);
+ if (sc->sc_ks_txpower)
+ kstat_install(sc->sc_ks_txpower);
+ if (sc->sc_ks_toff)
+ kstat_install(sc->sc_ks_toff);
+
+ sc->sc_flags |= IWN_FLAG_ATTACHED;
+
+ return (DDI_SUCCESS);
+
+ /* Free allocated memory if something failed during attachment. */
+fail_minor:
+ mac_unregister(ic->ic_mach);
+
+fail_mac_alloc:
+ ieee80211_detach(ic);
+ iwn_free_rx_ring(sc, &sc->rxq);
+
+fail_rxring:
+ for (i = 0; i < sc->ntxqs; i++)
+ iwn_free_tx_ring(sc, &sc->txq[i]);
+
+fail_txring:
+ iwn_free_sched(sc);
+
+fail_sched:
+ if (sc->ict != NULL)
+ iwn_free_ict(sc);
+
+fail_ict:
+ iwn_free_kw(sc);
+
+fail_kw:
+ iwn_free_fwmem(sc);
+
+fail_fwmem:
+fail_hw:
+ iwn_intr_teardown(sc);
+
+ iwn_kstat_free(sc->sc_ks_txpower, sc->sc_txpower,
+ sizeof (struct iwn_ks_txpower));
+
+ if (sc->hw_type == IWN_HW_REV_TYPE_6005)
+ iwn_kstat_free(sc->sc_ks_toff, sc->sc_toff.t6000,
+ sizeof (struct iwn_ks_toff_6000));
+ else
+ iwn_kstat_free(sc->sc_ks_toff, sc->sc_toff.t2000,
+ sizeof (struct iwn_ks_toff_2000));
+
+fail_intr:
+ ddi_regs_map_free(&sc->sc_regh);
+
+fail_regs_map:
+fail_pci_capab:
+ pci_config_teardown(&sc->sc_pcih);
+
+fail_pci_config:
+ iwn_kstat_free(sc->sc_ks_misc, sc->sc_misc,
+ sizeof (struct iwn_ks_misc));
+ iwn_kstat_free(sc->sc_ks_ant, sc->sc_ant,
+ sizeof (struct iwn_ks_ant));
+ iwn_kstat_free(sc->sc_ks_sens, sc->sc_sens,
+ sizeof (struct iwn_ks_sens));
+ iwn_kstat_free(sc->sc_ks_timing, sc->sc_timing,
+ sizeof (struct iwn_ks_timing));
+ iwn_kstat_free(sc->sc_ks_edca, sc->sc_edca,
+ sizeof (struct iwn_ks_edca));
+
+ ddi_soft_state_free(iwn_state, instance);
+
+ return (DDI_FAILURE);
+}
+
+int
+iwn4965_attach(struct iwn_softc *sc)
+{
+ struct iwn_ops *ops = &sc->ops;
+
+ ops->load_firmware = iwn4965_load_firmware;
+ ops->read_eeprom = iwn4965_read_eeprom;
+ ops->post_alive = iwn4965_post_alive;
+ ops->nic_config = iwn4965_nic_config;
+ ops->config_bt_coex = iwn_config_bt_coex_bluetooth;
+ ops->update_sched = iwn4965_update_sched;
+ ops->get_temperature = iwn4965_get_temperature;
+ ops->get_rssi = iwn4965_get_rssi;
+ ops->set_txpower = iwn4965_set_txpower;
+ ops->init_gains = iwn4965_init_gains;
+ ops->set_gains = iwn4965_set_gains;
+ ops->add_node = iwn4965_add_node;
+ ops->tx_done = iwn4965_tx_done;
+#ifndef IEEE80211_NO_HT
+ ops->ampdu_tx_start = iwn4965_ampdu_tx_start;
+ ops->ampdu_tx_stop = iwn4965_ampdu_tx_stop;
+#endif
+ sc->ntxqs = IWN4965_NTXQUEUES;
+ sc->ndmachnls = IWN4965_NDMACHNLS;
+ sc->broadcast_id = IWN4965_ID_BROADCAST;
+ sc->rxonsz = IWN4965_RXONSZ;
+ sc->schedsz = IWN4965_SCHEDSZ;
+ sc->fw_text_maxsz = IWN4965_FW_TEXT_MAXSZ;
+ sc->fw_data_maxsz = IWN4965_FW_DATA_MAXSZ;
+ sc->fwsz = IWN4965_FWSZ;
+ sc->sched_txfact_addr = IWN4965_SCHED_TXFACT;
+ sc->limits = &iwn4965_sensitivity_limits;
+ sc->fwname = "iwlwifi-4965-2.ucode";
+ /* Override chains masks, ROM is known to be broken. */
+ sc->txchainmask = IWN_ANT_AB;
+ sc->rxchainmask = IWN_ANT_ABC;
+
+ iwn_kstat_create(sc, "txpower", sizeof (struct iwn_ks_txpower),
+ &sc->sc_ks_txpower, (void **)&sc->sc_txpower);
+ iwn_kstat_init_4965(sc);
+
+ return 0;
+}
+
+int
+iwn5000_attach(struct iwn_softc *sc, uint16_t pid)
+{
+ struct iwn_ops *ops = &sc->ops;
+
+ ops->load_firmware = iwn5000_load_firmware;
+ ops->read_eeprom = iwn5000_read_eeprom;
+ ops->post_alive = iwn5000_post_alive;
+ ops->nic_config = iwn5000_nic_config;
+ ops->config_bt_coex = iwn_config_bt_coex_bluetooth;
+ ops->update_sched = iwn5000_update_sched;
+ ops->get_temperature = iwn5000_get_temperature;
+ ops->get_rssi = iwn5000_get_rssi;
+ ops->set_txpower = iwn5000_set_txpower;
+ ops->init_gains = iwn5000_init_gains;
+ ops->set_gains = iwn5000_set_gains;
+ ops->add_node = iwn5000_add_node;
+ ops->tx_done = iwn5000_tx_done;
+#ifndef IEEE80211_NO_HT
+ ops->ampdu_tx_start = iwn5000_ampdu_tx_start;
+ ops->ampdu_tx_stop = iwn5000_ampdu_tx_stop;
+#endif
+ sc->ntxqs = IWN5000_NTXQUEUES;
+ sc->ndmachnls = IWN5000_NDMACHNLS;
+ sc->broadcast_id = IWN5000_ID_BROADCAST;
+ sc->rxonsz = IWN5000_RXONSZ;
+ sc->schedsz = IWN5000_SCHEDSZ;
+ sc->fw_text_maxsz = IWN5000_FW_TEXT_MAXSZ;
+ sc->fw_data_maxsz = IWN5000_FW_DATA_MAXSZ;
+ sc->fwsz = IWN5000_FWSZ;
+ sc->sched_txfact_addr = IWN5000_SCHED_TXFACT;
+
+ switch (sc->hw_type) {
+ case IWN_HW_REV_TYPE_5100:
+ sc->limits = &iwn5000_sensitivity_limits;
+ sc->fwname = "iwlwifi-5000-2.ucode";
+ /* Override chains masks, ROM is known to be broken. */
+ sc->txchainmask = IWN_ANT_B;
+ sc->rxchainmask = IWN_ANT_AB;
+ break;
+ case IWN_HW_REV_TYPE_5150:
+ sc->limits = &iwn5150_sensitivity_limits;
+ sc->fwname = "iwlwifi-5150-2.ucode";
+ break;
+ case IWN_HW_REV_TYPE_5300:
+ case IWN_HW_REV_TYPE_5350:
+ sc->limits = &iwn5000_sensitivity_limits;
+ sc->fwname = "iwlwifi-5000-2.ucode";
+ break;
+ case IWN_HW_REV_TYPE_1000:
+ sc->limits = &iwn1000_sensitivity_limits;
+ if (pid == PCI_PRODUCT_INTEL_WIFI_LINK_100_1 ||
+ pid == PCI_PRODUCT_INTEL_WIFI_LINK_100_2)
+ sc->fwname = "iwlwifi-100-5.ucode";
+ else
+ sc->fwname = "iwlwifi-1000-3.ucode";
+ break;
+ case IWN_HW_REV_TYPE_6000:
+ sc->limits = &iwn6000_sensitivity_limits;
+ sc->fwname = "iwlwifi-6000-4.ucode";
+ if (pid == PCI_PRODUCT_INTEL_WIFI_LINK_6000_IPA_1 ||
+ pid == PCI_PRODUCT_INTEL_WIFI_LINK_6000_IPA_2) {
+ sc->sc_flags |= IWN_FLAG_INTERNAL_PA;
+ /* Override chains masks, ROM is known to be broken. */
+ sc->txchainmask = IWN_ANT_BC;
+ sc->rxchainmask = IWN_ANT_BC;
+ }
+ break;
+ case IWN_HW_REV_TYPE_6050:
+ sc->limits = &iwn6000_sensitivity_limits;
+ sc->fwname = "iwlwifi-6050-5.ucode";
+ break;
+ case IWN_HW_REV_TYPE_6005:
+ sc->limits = &iwn6000_sensitivity_limits;
+ /* Type 6030 cards return IWN_HW_REV_TYPE_6005 */
+ if (pid == PCI_PRODUCT_INTEL_WIFI_LINK_1030_1 ||
+ pid == PCI_PRODUCT_INTEL_WIFI_LINK_1030_2 ||
+ pid == PCI_PRODUCT_INTEL_WIFI_LINK_6230_1 ||
+ pid == PCI_PRODUCT_INTEL_WIFI_LINK_6230_2 ||
+ pid == PCI_PRODUCT_INTEL_WIFI_LINK_6235 ||
+ pid == PCI_PRODUCT_INTEL_WIFI_LINK_6235_2) {
+ sc->fwname = "iwlwifi-6000g2b-6.ucode";
+ ops->config_bt_coex = iwn_config_bt_coex_adv1;
+ }
+ else
+ sc->fwname = "iwlwifi-6000g2a-5.ucode";
+
+ iwn_kstat_create(sc, "temp_offset",
+ sizeof (struct iwn_ks_toff_6000),
+ &sc->sc_ks_toff, (void **)&sc->sc_toff.t6000);
+ iwn_kstat_init_6000(sc);
+ break;
+ case IWN_HW_REV_TYPE_2030:
+ sc->limits = &iwn2000_sensitivity_limits;
+ sc->fwname = "iwlwifi-2030-6.ucode";
+ ops->config_bt_coex = iwn_config_bt_coex_adv2;
+
+ iwn_kstat_create(sc, "temp_offset",
+ sizeof (struct iwn_ks_toff_2000),
+ &sc->sc_ks_toff, (void **)&sc->sc_toff.t2000);
+ iwn_kstat_init_2000(sc);
+ break;
+ case IWN_HW_REV_TYPE_2000:
+ sc->limits = &iwn2000_sensitivity_limits;
+ sc->fwname = "iwlwifi-2000-6.ucode";
+
+ iwn_kstat_create(sc, "temp_offset",
+ sizeof (struct iwn_ks_toff_2000),
+ &sc->sc_ks_toff, (void **)&sc->sc_toff.t2000);
+ iwn_kstat_init_2000(sc);
+ break;
+ case IWN_HW_REV_TYPE_135:
+ sc->limits = &iwn2000_sensitivity_limits;
+ sc->fwname = "iwlwifi-135-6.ucode";
+ ops->config_bt_coex = iwn_config_bt_coex_adv2;
+
+ iwn_kstat_create(sc, "temp_offset",
+ sizeof (struct iwn_ks_toff_2000),
+ &sc->sc_ks_toff, (void **)&sc->sc_toff.t2000);
+ iwn_kstat_init_2000(sc);
+ break;
+ case IWN_HW_REV_TYPE_105:
+ sc->limits = &iwn2000_sensitivity_limits;
+ sc->fwname = "iwlwifi-105-6.ucode";
+
+ iwn_kstat_create(sc, "temp_offset",
+ sizeof (struct iwn_ks_toff_2000),
+ &sc->sc_ks_toff, (void **)&sc->sc_toff.t2000);
+ iwn_kstat_init_2000(sc);
+ break;
+ default:
+ dev_err(sc->sc_dip, CE_WARN, "!adapter type %d not supported",
+ sc->hw_type);
+ return ENOTSUP;
+ }
+ return 0;
+}
+
+static int
+iwn_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ struct iwn_softc *sc = ddi_get_driver_private(dip);
+ ieee80211com_t *ic = &sc->sc_ic;
+ int qid, error;
+
+ switch (cmd) {
+ case DDI_DETACH:
+ break;
+ case DDI_SUSPEND:
+ sc->sc_flags &= ~IWN_FLAG_HW_ERR_RECOVER;
+ sc->sc_flags &= ~IWN_FLAG_RATE_AUTO_CTL;
+
+ sc->sc_flags |= IWN_FLAG_SUSPEND;
+
+ if (sc->sc_flags & IWN_FLAG_RUNNING) {
+ iwn_hw_stop(sc, B_TRUE);
+ ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
+
+ }
+
+ return (DDI_SUCCESS);
+ default:
+ return (DDI_FAILURE);
+ }
+
+ if (!(sc->sc_flags & IWN_FLAG_ATTACHED)) {
+ return (DDI_FAILURE);
+ }
+
+ error = mac_disable(ic->ic_mach);
+ if (error != DDI_SUCCESS)
+ return (error);
+
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags |= IWN_FLAG_STOP_CALIB_TO;
+ mutex_exit(&sc->sc_mtx);
+
+ if (sc->calib_to != 0)
+ (void) untimeout(sc->calib_to);
+ sc->calib_to = 0;
+
+ if (sc->scan_to != 0)
+ (void) untimeout(sc->scan_to);
+ sc->scan_to = 0;
+
+ ddi_periodic_delete(sc->sc_periodic);
+
+ /*
+ * stop chipset
+ */
+ iwn_hw_stop(sc, B_TRUE);
+
+ /*
+ * Unregister from GLD
+ */
+ (void) mac_unregister(ic->ic_mach);
+ ieee80211_detach(ic);
+
+ /* Uninstall interrupt handler. */
+ iwn_intr_teardown(sc);
+
+ /* Free DMA resources. */
+ mutex_enter(&sc->sc_mtx);
+ iwn_free_rx_ring(sc, &sc->rxq);
+ for (qid = 0; qid < sc->ntxqs; qid++)
+ iwn_free_tx_ring(sc, &sc->txq[qid]);
+ iwn_free_sched(sc);
+ iwn_free_kw(sc);
+ if (sc->ict != NULL)
+ iwn_free_ict(sc);
+ iwn_free_fwmem(sc);
+ mutex_exit(&sc->sc_mtx);
+
+ iwn_kstat_free(sc->sc_ks_misc, sc->sc_misc,
+ sizeof (struct iwn_ks_misc));
+ iwn_kstat_free(sc->sc_ks_ant, sc->sc_ant,
+ sizeof (struct iwn_ks_ant));
+ iwn_kstat_free(sc->sc_ks_sens, sc->sc_sens,
+ sizeof (struct iwn_ks_sens));
+ iwn_kstat_free(sc->sc_ks_timing, sc->sc_timing,
+ sizeof (struct iwn_ks_timing));
+ iwn_kstat_free(sc->sc_ks_edca, sc->sc_edca,
+ sizeof (struct iwn_ks_edca));
+ iwn_kstat_free(sc->sc_ks_txpower, sc->sc_txpower,
+ sizeof (struct iwn_ks_txpower));
+
+ if (sc->hw_type == IWN_HW_REV_TYPE_6005)
+ iwn_kstat_free(sc->sc_ks_toff, sc->sc_toff.t6000,
+ sizeof (struct iwn_ks_toff_6000));
+ else
+ iwn_kstat_free(sc->sc_ks_toff, sc->sc_toff.t2000,
+ sizeof (struct iwn_ks_toff_2000));
+
+ ddi_regs_map_free(&sc->sc_regh);
+ pci_config_teardown(&sc->sc_pcih);
+ ddi_remove_minor_node(dip, NULL);
+ ddi_soft_state_free(iwn_state, ddi_get_instance(dip));
+
+ return 0;
+}
+
+static int
+iwn_quiesce(dev_info_t *dip)
+{
+ struct iwn_softc *sc;
+
+ sc = ddi_get_soft_state(iwn_state, ddi_get_instance(dip));
+ if (sc == NULL)
+ return (DDI_FAILURE);
+
+#ifdef IWN_DEBUG
+ /* bypass any messages */
+ iwn_dbg_print = 0;
+#endif
+
+ /*
+ * No more blocking is allowed while we are in the
+ * quiesce(9E) entry point.
+ */
+ sc->sc_flags |= IWN_FLAG_QUIESCED;
+
+ /*
+ * Disable and mask all interrupts.
+ */
+ iwn_hw_stop(sc, B_FALSE);
+
+ return (DDI_SUCCESS);
+}
+
+static int
+iwn_nic_lock(struct iwn_softc *sc)
+{
+ int ntries;
+
+ /* Request exclusive access to NIC. */
+ IWN_SETBITS(sc, IWN_GP_CNTRL, IWN_GP_CNTRL_MAC_ACCESS_REQ);
+
+ /* Spin until we actually get the lock. */
+ for (ntries = 0; ntries < 1000; ntries++) {
+ if ((IWN_READ(sc, IWN_GP_CNTRL) &
+ (IWN_GP_CNTRL_MAC_ACCESS_ENA | IWN_GP_CNTRL_SLEEP)) ==
+ IWN_GP_CNTRL_MAC_ACCESS_ENA)
+ return 0;
+ DELAY(10);
+ }
+ return ETIMEDOUT;
+}
+
+static __inline void
+iwn_nic_unlock(struct iwn_softc *sc)
+{
+ IWN_CLRBITS(sc, IWN_GP_CNTRL, IWN_GP_CNTRL_MAC_ACCESS_REQ);
+}
+
+static __inline uint32_t
+iwn_prph_read(struct iwn_softc *sc, uint32_t addr)
+{
+ IWN_WRITE(sc, IWN_PRPH_RADDR, IWN_PRPH_DWORD | addr);
+ IWN_BARRIER_READ_WRITE(sc);
+ return IWN_READ(sc, IWN_PRPH_RDATA);
+}
+
+static __inline void
+iwn_prph_write(struct iwn_softc *sc, uint32_t addr, uint32_t data)
+{
+ IWN_WRITE(sc, IWN_PRPH_WADDR, IWN_PRPH_DWORD | addr);
+ IWN_BARRIER_WRITE(sc);
+ IWN_WRITE(sc, IWN_PRPH_WDATA, data);
+}
+
+static __inline void
+iwn_prph_setbits(struct iwn_softc *sc, uint32_t addr, uint32_t mask)
+{
+ iwn_prph_write(sc, addr, iwn_prph_read(sc, addr) | mask);
+}
+
+static __inline void
+iwn_prph_clrbits(struct iwn_softc *sc, uint32_t addr, uint32_t mask)
+{
+ iwn_prph_write(sc, addr, iwn_prph_read(sc, addr) & ~mask);
+}
+
+static __inline void
+iwn_prph_write_region_4(struct iwn_softc *sc, uint32_t addr,
+ const uint32_t *data, int count)
+{
+ for (; count > 0; count--, data++, addr += 4)
+ iwn_prph_write(sc, addr, *data);
+}
+
+static __inline uint32_t
+iwn_mem_read(struct iwn_softc *sc, uint32_t addr)
+{
+ IWN_WRITE(sc, IWN_MEM_RADDR, addr);
+ IWN_BARRIER_READ_WRITE(sc);
+ return IWN_READ(sc, IWN_MEM_RDATA);
+}
+
+static __inline void
+iwn_mem_write(struct iwn_softc *sc, uint32_t addr, uint32_t data)
+{
+ IWN_WRITE(sc, IWN_MEM_WADDR, addr);
+ IWN_BARRIER_WRITE(sc);
+ IWN_WRITE(sc, IWN_MEM_WDATA, data);
+}
+
+#ifndef IEEE80211_NO_HT
+static __inline void
+iwn_mem_write_2(struct iwn_softc *sc, uint32_t addr, uint16_t data)
+{
+ uint32_t tmp;
+
+ tmp = iwn_mem_read(sc, addr & ~3);
+ if (addr & 3)
+ tmp = (tmp & 0x0000ffff) | data << 16;
+ else
+ tmp = (tmp & 0xffff0000) | data;
+ iwn_mem_write(sc, addr & ~3, tmp);
+}
+#endif
+
+static __inline void
+iwn_mem_read_region_4(struct iwn_softc *sc, uint32_t addr, uint32_t *data,
+ int count)
+{
+ for (; count > 0; count--, addr += 4)
+ *data++ = iwn_mem_read(sc, addr);
+}
+
+static __inline void
+iwn_mem_set_region_4(struct iwn_softc *sc, uint32_t addr, uint32_t val,
+ int count)
+{
+ for (; count > 0; count--, addr += 4)
+ iwn_mem_write(sc, addr, val);
+}
+
+static int
+iwn_eeprom_lock(struct iwn_softc *sc)
+{
+ int i, ntries;
+
+ for (i = 0; i < 100; i++) {
+ /* Request exclusive access to EEPROM. */
+ IWN_SETBITS(sc, IWN_HW_IF_CONFIG,
+ IWN_HW_IF_CONFIG_EEPROM_LOCKED);
+
+ /* Spin until we actually get the lock. */
+ for (ntries = 0; ntries < 100; ntries++) {
+ if (IWN_READ(sc, IWN_HW_IF_CONFIG) &
+ IWN_HW_IF_CONFIG_EEPROM_LOCKED)
+ return 0;
+ DELAY(10);
+ }
+ }
+ return ETIMEDOUT;
+}
+
+static __inline void
+iwn_eeprom_unlock(struct iwn_softc *sc)
+{
+ IWN_CLRBITS(sc, IWN_HW_IF_CONFIG, IWN_HW_IF_CONFIG_EEPROM_LOCKED);
+}
+
+/*
+ * Initialize access by host to One Time Programmable ROM.
+ * NB: This kind of ROM can be found on 1000 or 6000 Series only.
+ */
+static int
+iwn_init_otprom(struct iwn_softc *sc)
+{
+ uint16_t prev = 0, base, next;
+ int count, error;
+
+ /* Wait for clock stabilization before accessing prph. */
+ if ((error = iwn_clock_wait(sc)) != 0)
+ return error;
+
+ if ((error = iwn_nic_lock(sc)) != 0)
+ return error;
+ iwn_prph_setbits(sc, IWN_APMG_PS, IWN_APMG_PS_RESET_REQ);
+ DELAY(5);
+ iwn_prph_clrbits(sc, IWN_APMG_PS, IWN_APMG_PS_RESET_REQ);
+ iwn_nic_unlock(sc);
+
+ /* Set auto clock gate disable bit for HW with OTP shadow RAM. */
+ if (sc->hw_type != IWN_HW_REV_TYPE_1000) {
+ IWN_SETBITS(sc, IWN_DBG_LINK_PWR_MGMT,
+ IWN_RESET_LINK_PWR_MGMT_DIS);
+ }
+ IWN_CLRBITS(sc, IWN_EEPROM_GP, IWN_EEPROM_GP_IF_OWNER);
+ /* Clear ECC status. */
+ IWN_SETBITS(sc, IWN_OTP_GP,
+ IWN_OTP_GP_ECC_CORR_STTS | IWN_OTP_GP_ECC_UNCORR_STTS);
+
+ /*
+ * Find the block before last block (contains the EEPROM image)
+ * for HW without OTP shadow RAM.
+ */
+ if (sc->hw_type == IWN_HW_REV_TYPE_1000) {
+ /* Switch to absolute addressing mode. */
+ IWN_CLRBITS(sc, IWN_OTP_GP, IWN_OTP_GP_RELATIVE_ACCESS);
+ base = 0;
+ for (count = 0; count < IWN1000_OTP_NBLOCKS; count++) {
+ error = iwn_read_prom_data(sc, base, &next, 2);
+ if (error != 0)
+ return error;
+ if (next == 0) /* End of linked-list. */
+ break;
+ prev = base;
+ base = le16toh(next);
+ }
+ if (count == 0 || count == IWN1000_OTP_NBLOCKS)
+ return EIO;
+ /* Skip "next" word. */
+ sc->prom_base = prev + 1;
+ }
+ return 0;
+}
+
+static int
+iwn_read_prom_data(struct iwn_softc *sc, uint32_t addr, void *data, int count)
+{
+ uint8_t *out = data;
+ uint32_t val, tmp;
+ int ntries;
+
+ addr += sc->prom_base;
+ for (; count > 0; count -= 2, addr++) {
+ IWN_WRITE(sc, IWN_EEPROM, addr << 2);
+ for (ntries = 0; ntries < 10; ntries++) {
+ val = IWN_READ(sc, IWN_EEPROM);
+ if (val & IWN_EEPROM_READ_VALID)
+ break;
+ DELAY(5);
+ }
+ if (ntries == 10) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!timeout reading ROM at 0x%x", addr);
+ return ETIMEDOUT;
+ }
+ if (sc->sc_flags & IWN_FLAG_HAS_OTPROM) {
+ /* OTPROM, check for ECC errors. */
+ tmp = IWN_READ(sc, IWN_OTP_GP);
+ if (tmp & IWN_OTP_GP_ECC_UNCORR_STTS) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!OTPROM ECC error at 0x%x", addr);
+ return EIO;
+ }
+ if (tmp & IWN_OTP_GP_ECC_CORR_STTS) {
+ /* Correctable ECC error, clear bit. */
+ IWN_SETBITS(sc, IWN_OTP_GP,
+ IWN_OTP_GP_ECC_CORR_STTS);
+ }
+ }
+ *out++ = val >> 16;
+ if (count > 1)
+ *out++ = val >> 24;
+ }
+ return 0;
+}
+
+static int
+iwn_dma_contig_alloc(struct iwn_softc *sc, struct iwn_dma_info *dma,
+ uint_t size, uint_t flags, void **kvap, ddi_device_acc_attr_t *acc_attr,
+ uint_t align)
+{
+ ddi_dma_attr_t dma_attr = {
+ .dma_attr_version = DMA_ATTR_V0,
+ .dma_attr_addr_lo = 0,
+ .dma_attr_addr_hi = 0xfffffffffULL,
+ .dma_attr_count_max = 0xfffffffffULL,
+ .dma_attr_align = align,
+ .dma_attr_burstsizes = 0x7ff,
+ .dma_attr_minxfer = 1,
+ .dma_attr_maxxfer = 0xfffffffffULL,
+ .dma_attr_seg = 0xfffffffffULL,
+ .dma_attr_sgllen = 1,
+ .dma_attr_granular = 1,
+ .dma_attr_flags = 0,
+ };
+ int error;
+
+ error = ddi_dma_alloc_handle(sc->sc_dip, &dma_attr, DDI_DMA_SLEEP, NULL,
+ &dma->dma_hdl);
+ if (error != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "ddi_dma_alloc_handle() failed, error = %d", error);
+ goto fail;
+ }
+
+ error = ddi_dma_mem_alloc(dma->dma_hdl, size, acc_attr,
+ flags & (DDI_DMA_CONSISTENT | DDI_DMA_STREAMING), DDI_DMA_SLEEP, 0,
+ &dma->vaddr, &dma->length, &dma->acc_hdl);
+ if (error != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "ddi_dma_mem_alloc() failed, error = %d", error);
+ goto fail2;
+ }
+
+ bzero(dma->vaddr, dma->length);
+
+ error = ddi_dma_addr_bind_handle(dma->dma_hdl, NULL, dma->vaddr,
+ dma->length, flags, DDI_DMA_SLEEP, NULL, &dma->cookie,
+ &dma->ncookies);
+ if (error != DDI_DMA_MAPPED) {
+ dma->ncookies = 0;
+ dev_err(sc->sc_dip, CE_WARN,
+ "ddi_dma_addr_bind_handle() failed, error = %d", error);
+ goto fail3;
+ }
+
+ dma->size = size;
+ dma->paddr = dma->cookie.dmac_laddress;
+
+ if (kvap != NULL)
+ *kvap = (void *)dma->vaddr;
+
+ return (DDI_SUCCESS);
+
+fail3:
+ ddi_dma_mem_free(&dma->acc_hdl);
+fail2:
+ ddi_dma_free_handle(&dma->dma_hdl);
+fail:
+ bzero(dma, sizeof (struct iwn_dma_info));
+ return (DDI_FAILURE);
+}
+
+static void
+iwn_dma_contig_free(struct iwn_dma_info *dma)
+{
+ if (dma->dma_hdl != NULL) {
+ if (dma->ncookies)
+ (void) ddi_dma_unbind_handle(dma->dma_hdl);
+ ddi_dma_free_handle(&dma->dma_hdl);
+ }
+
+ if (dma->acc_hdl != NULL)
+ ddi_dma_mem_free(&dma->acc_hdl);
+
+ bzero(dma, sizeof (struct iwn_dma_info));
+}
+
+static int
+iwn_alloc_sched(struct iwn_softc *sc)
+{
+ /* TX scheduler rings must be aligned on a 1KB boundary. */
+
+ return iwn_dma_contig_alloc(sc, &sc->sched_dma, sc->schedsz,
+ DDI_DMA_CONSISTENT | DDI_DMA_RDWR, (void **)&sc->sched,
+ &iwn_dma_accattr, 1024);
+}
+
+static void
+iwn_free_sched(struct iwn_softc *sc)
+{
+ iwn_dma_contig_free(&sc->sched_dma);
+}
+
+static int
+iwn_alloc_kw(struct iwn_softc *sc)
+{
+ /* "Keep Warm" page must be aligned on a 4KB boundary. */
+
+ return iwn_dma_contig_alloc(sc, &sc->kw_dma, IWN_KW_SIZE,
+ DDI_DMA_CONSISTENT | DDI_DMA_RDWR, NULL, &iwn_dma_accattr, 4096);
+}
+
+static void
+iwn_free_kw(struct iwn_softc *sc)
+{
+ iwn_dma_contig_free(&sc->kw_dma);
+}
+
+static int
+iwn_alloc_ict(struct iwn_softc *sc)
+{
+ /* ICT table must be aligned on a 4KB boundary. */
+
+ return iwn_dma_contig_alloc(sc, &sc->ict_dma, IWN_ICT_SIZE,
+ DDI_DMA_CONSISTENT | DDI_DMA_RDWR, (void **)&sc->ict,
+ &iwn_dma_descattr, 4096);
+}
+
+static void
+iwn_free_ict(struct iwn_softc *sc)
+{
+ iwn_dma_contig_free(&sc->ict_dma);
+}
+
+static int
+iwn_alloc_fwmem(struct iwn_softc *sc)
+{
+ /* Must be aligned on a 16-byte boundary. */
+ return iwn_dma_contig_alloc(sc, &sc->fw_dma, sc->fwsz,
+ DDI_DMA_CONSISTENT | DDI_DMA_RDWR, NULL, &iwn_dma_accattr, 16);
+}
+
+static void
+iwn_free_fwmem(struct iwn_softc *sc)
+{
+ iwn_dma_contig_free(&sc->fw_dma);
+}
+
+static int
+iwn_alloc_rx_ring(struct iwn_softc *sc, struct iwn_rx_ring *ring)
+{
+ size_t size;
+ int i, error;
+
+ ring->cur = 0;
+
+ /* Allocate RX descriptors (256-byte aligned). */
+ size = IWN_RX_RING_COUNT * sizeof (uint32_t);
+ error = iwn_dma_contig_alloc(sc, &ring->desc_dma, size,
+ DDI_DMA_CONSISTENT | DDI_DMA_RDWR, (void **)&ring->desc,
+ &iwn_dma_descattr, 256);
+ if (error != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not allocate RX ring DMA memory");
+ goto fail;
+ }
+
+ /* Allocate RX status area (16-byte aligned). */
+ error = iwn_dma_contig_alloc(sc, &ring->stat_dma,
+ sizeof (struct iwn_rx_status), DDI_DMA_CONSISTENT | DDI_DMA_RDWR,
+ (void **)&ring->stat, &iwn_dma_descattr, 16);
+ if (error != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not allocate RX status DMA memory");
+ goto fail;
+ }
+
+ /*
+ * Allocate and map RX buffers.
+ */
+ for (i = 0; i < IWN_RX_RING_COUNT; i++) {
+ struct iwn_rx_data *data = &ring->data[i];
+
+ error = iwn_dma_contig_alloc(sc, &data->dma_data, IWN_RBUF_SIZE,
+ DDI_DMA_CONSISTENT | DDI_DMA_READ, NULL, &iwn_dma_accattr,
+ 256);
+ if (error != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not create RX buf DMA map");
+ goto fail;
+ }
+
+ /* Set physical address of RX buffer (256-byte aligned). */
+ ring->desc[i] = htole32(data->dma_data.paddr >> 8);
+ }
+
+ (void) ddi_dma_sync(ring->desc_dma.dma_hdl, 0, 0, DDI_DMA_SYNC_FORDEV);
+
+ return 0;
+
+fail: iwn_free_rx_ring(sc, ring);
+ return error;
+}
+
+static void
+iwn_reset_rx_ring(struct iwn_softc *sc, struct iwn_rx_ring *ring)
+{
+ int ntries;
+
+ if (iwn_nic_lock(sc) == 0) {
+ IWN_WRITE(sc, IWN_FH_RX_CONFIG, 0);
+ for (ntries = 0; ntries < 1000; ntries++) {
+ if (IWN_READ(sc, IWN_FH_RX_STATUS) &
+ IWN_FH_RX_STATUS_IDLE)
+ break;
+ DELAY(10);
+ }
+ iwn_nic_unlock(sc);
+ }
+ ring->cur = 0;
+ sc->last_rx_valid = 0;
+}
+
+static void
+iwn_free_rx_ring(struct iwn_softc *sc, struct iwn_rx_ring *ring)
+{
+ _NOTE(ARGUNUSED(sc));
+ int i;
+
+ iwn_dma_contig_free(&ring->desc_dma);
+ iwn_dma_contig_free(&ring->stat_dma);
+
+ for (i = 0; i < IWN_RX_RING_COUNT; i++) {
+ struct iwn_rx_data *data = &ring->data[i];
+
+ if (data->dma_data.dma_hdl)
+ iwn_dma_contig_free(&data->dma_data);
+ }
+}
+
+static int
+iwn_alloc_tx_ring(struct iwn_softc *sc, struct iwn_tx_ring *ring, int qid)
+{
+ uintptr_t paddr;
+ size_t size;
+ int i, error;
+
+ ring->qid = qid;
+ ring->queued = 0;
+ ring->cur = 0;
+
+ /* Allocate TX descriptors (256-byte aligned). */
+ size = IWN_TX_RING_COUNT * sizeof (struct iwn_tx_desc);
+ error = iwn_dma_contig_alloc(sc, &ring->desc_dma, size,
+ DDI_DMA_CONSISTENT | DDI_DMA_WRITE, (void **)&ring->desc,
+ &iwn_dma_descattr, 256);
+ if (error != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not allocate TX ring DMA memory");
+ goto fail;
+ }
+ /*
+ * We only use rings 0 through 4 (4 EDCA + cmd) so there is no need
+ * to allocate commands space for other rings.
+ * XXX Do we really need to allocate descriptors for other rings?
+ */
+ if (qid > 4)
+ return 0;
+
+ size = IWN_TX_RING_COUNT * sizeof (struct iwn_tx_cmd);
+ error = iwn_dma_contig_alloc(sc, &ring->cmd_dma, size,
+ DDI_DMA_CONSISTENT | DDI_DMA_WRITE, (void **)&ring->cmd,
+ &iwn_dma_accattr, 4);
+ if (error != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not allocate TX cmd DMA memory");
+ goto fail;
+ }
+
+ paddr = ring->cmd_dma.paddr;
+ for (i = 0; i < IWN_TX_RING_COUNT; i++) {
+ struct iwn_tx_data *data = &ring->data[i];
+
+ data->cmd_paddr = paddr;
+ data->scratch_paddr = paddr + 12;
+ paddr += sizeof (struct iwn_tx_cmd);
+
+ error = iwn_dma_contig_alloc(sc, &data->dma_data, IWN_TBUF_SIZE,
+ DDI_DMA_CONSISTENT | DDI_DMA_WRITE, NULL, &iwn_dma_accattr,
+ 256);
+ if (error != DDI_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not create TX buf DMA map");
+ goto fail;
+ }
+ }
+ return 0;
+
+fail: iwn_free_tx_ring(sc, ring);
+ return error;
+}
+
+static void
+iwn_reset_tx_ring(struct iwn_softc *sc, struct iwn_tx_ring *ring)
+{
+ int i;
+
+ if (ring->qid < 4)
+ for (i = 0; i < IWN_TX_RING_COUNT; i++) {
+ struct iwn_tx_data *data = &ring->data[i];
+
+ (void) ddi_dma_sync(data->dma_data.dma_hdl, 0, 0,
+ DDI_DMA_SYNC_FORDEV);
+ }
+
+ /* Clear TX descriptors. */
+ memset(ring->desc, 0, ring->desc_dma.size);
+ (void) ddi_dma_sync(ring->desc_dma.dma_hdl, 0, 0, DDI_DMA_SYNC_FORDEV);
+ sc->qfullmsk &= ~(1 << ring->qid);
+ ring->queued = 0;
+ ring->cur = 0;
+}
+
+static void
+iwn_free_tx_ring(struct iwn_softc *sc, struct iwn_tx_ring *ring)
+{
+ _NOTE(ARGUNUSED(sc));
+ int i;
+
+ iwn_dma_contig_free(&ring->desc_dma);
+ iwn_dma_contig_free(&ring->cmd_dma);
+
+ for (i = 0; i < IWN_TX_RING_COUNT; i++) {
+ struct iwn_tx_data *data = &ring->data[i];
+
+ if (data->dma_data.dma_hdl)
+ iwn_dma_contig_free(&data->dma_data);
+ }
+}
+
+static void
+iwn5000_ict_reset(struct iwn_softc *sc)
+{
+ /* Disable interrupts. */
+ IWN_WRITE(sc, IWN_INT_MASK, 0);
+
+ /* Reset ICT table. */
+ memset(sc->ict, 0, IWN_ICT_SIZE);
+ sc->ict_cur = 0;
+
+ /* Set physical address of ICT table (4KB aligned). */
+ IWN_WRITE(sc, IWN_DRAM_INT_TBL, IWN_DRAM_INT_TBL_ENABLE |
+ IWN_DRAM_INT_TBL_WRAP_CHECK | sc->ict_dma.paddr >> 12);
+
+ /* Enable periodic RX interrupt. */
+ sc->int_mask |= IWN_INT_RX_PERIODIC;
+ /* Switch to ICT interrupt mode in driver. */
+ sc->sc_flags |= IWN_FLAG_USE_ICT;
+
+ /* Re-enable interrupts. */
+ IWN_WRITE(sc, IWN_INT, 0xffffffff);
+ IWN_WRITE(sc, IWN_INT_MASK, sc->int_mask);
+}
+
+static int
+iwn_read_eeprom(struct iwn_softc *sc)
+{
+ struct iwn_ops *ops = &sc->ops;
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint16_t val;
+ int error;
+
+ /* Check whether adapter has an EEPROM or an OTPROM. */
+ if (sc->hw_type >= IWN_HW_REV_TYPE_1000 &&
+ (IWN_READ(sc, IWN_OTP_GP) & IWN_OTP_GP_DEV_SEL_OTP))
+ sc->sc_flags |= IWN_FLAG_HAS_OTPROM;
+ IWN_DBG("%s found",
+ (sc->sc_flags & IWN_FLAG_HAS_OTPROM) ? "OTPROM" : "EEPROM");
+
+ /* Adapter has to be powered on for EEPROM access to work. */
+ if ((error = iwn_apm_init(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not power ON adapter");
+ return error;
+ }
+
+ if ((IWN_READ(sc, IWN_EEPROM_GP) & 0x7) == 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!bad ROM signature");
+ return EIO;
+ }
+ if ((error = iwn_eeprom_lock(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not lock ROM (error=%d)", error);
+ return error;
+ }
+ if (sc->sc_flags & IWN_FLAG_HAS_OTPROM) {
+ if ((error = iwn_init_otprom(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not initialize OTPROM");
+ return error;
+ }
+ }
+
+ iwn_read_prom_data(sc, IWN_EEPROM_SKU_CAP, &val, 2);
+ IWN_DBG("SKU capabilities=0x%04x", le16toh(val));
+ /* Check if HT support is bonded out. */
+ if (val & htole16(IWN_EEPROM_SKU_CAP_11N))
+ sc->sc_flags |= IWN_FLAG_HAS_11N;
+
+ iwn_read_prom_data(sc, IWN_EEPROM_RFCFG, &val, 2);
+ sc->rfcfg = le16toh(val);
+ IWN_DBG("radio config=0x%04x", sc->rfcfg);
+ /* Read Tx/Rx chains from ROM unless it's known to be broken. */
+ if (sc->txchainmask == 0)
+ sc->txchainmask = IWN_RFCFG_TXANTMSK(sc->rfcfg);
+ if (sc->rxchainmask == 0)
+ sc->rxchainmask = IWN_RFCFG_RXANTMSK(sc->rfcfg);
+
+ /* Read MAC address. */
+ iwn_read_prom_data(sc, IWN_EEPROM_MAC, ic->ic_macaddr, 6);
+
+ /* Read adapter-specific information from EEPROM. */
+ ops->read_eeprom(sc);
+
+ iwn_apm_stop(sc); /* Power OFF adapter. */
+
+ iwn_eeprom_unlock(sc);
+ return 0;
+}
+
+static void
+iwn4965_read_eeprom(struct iwn_softc *sc)
+{
+ uint32_t addr;
+ uint16_t val;
+ int i;
+
+ /* Read regulatory domain (4 ASCII characters). */
+ iwn_read_prom_data(sc, IWN4965_EEPROM_DOMAIN, sc->eeprom_domain, 4);
+
+ /* Read the list of authorized channels (20MHz ones only). */
+ for (i = 0; i < 5; i++) {
+ addr = iwn4965_regulatory_bands[i];
+ iwn_read_eeprom_channels(sc, i, addr);
+ }
+
+ /* Read maximum allowed TX power for 2GHz and 5GHz bands. */
+ iwn_read_prom_data(sc, IWN4965_EEPROM_MAXPOW, &val, 2);
+ sc->maxpwr2GHz = val & 0xff;
+ sc->maxpwr5GHz = val >> 8;
+ /* Check that EEPROM values are within valid range. */
+ if (sc->maxpwr5GHz < 20 || sc->maxpwr5GHz > 50)
+ sc->maxpwr5GHz = 38;
+ if (sc->maxpwr2GHz < 20 || sc->maxpwr2GHz > 50)
+ sc->maxpwr2GHz = 38;
+ IWN_DBG("maxpwr 2GHz=%d 5GHz=%d", sc->maxpwr2GHz, sc->maxpwr5GHz);
+
+ /* Read samples for each TX power group. */
+ iwn_read_prom_data(sc, IWN4965_EEPROM_BANDS, sc->bands,
+ sizeof sc->bands);
+
+ /* Read voltage at which samples were taken. */
+ iwn_read_prom_data(sc, IWN4965_EEPROM_VOLTAGE, &val, 2);
+ sc->eeprom_voltage = (int16_t)le16toh(val);
+ IWN_DBG("voltage=%d (in 0.3V)", sc->eeprom_voltage);
+
+#ifdef IWN_DEBUG
+ /* Print samples. */
+ if (iwn_dbg_print != 0) {
+ for (i = 0; i < IWN_NBANDS; i++)
+ iwn4965_print_power_group(sc, i);
+ }
+#endif
+}
+
+#ifdef IWN_DEBUG
+static void
+iwn4965_print_power_group(struct iwn_softc *sc, int i)
+{
+ struct iwn4965_eeprom_band *band = &sc->bands[i];
+ struct iwn4965_eeprom_chan_samples *chans = band->chans;
+ int j, c;
+
+ dev_err(sc->sc_dip, CE_CONT, "!===band %d===", i);
+ dev_err(sc->sc_dip, CE_CONT, "!chan lo=%d, chan hi=%d", band->lo,
+ band->hi);
+ dev_err(sc->sc_dip, CE_CONT, "!chan1 num=%d", chans[0].num);
+ for (c = 0; c < 2; c++) {
+ for (j = 0; j < IWN_NSAMPLES; j++) {
+ dev_err(sc->sc_dip, CE_CONT, "!chain %d, sample %d: "
+ "temp=%d gain=%d power=%d pa_det=%d", c, j,
+ chans[0].samples[c][j].temp,
+ chans[0].samples[c][j].gain,
+ chans[0].samples[c][j].power,
+ chans[0].samples[c][j].pa_det);
+ }
+ }
+ dev_err(sc->sc_dip, CE_CONT, "!chan2 num=%d", chans[1].num);
+ for (c = 0; c < 2; c++) {
+ for (j = 0; j < IWN_NSAMPLES; j++) {
+ dev_err(sc->sc_dip, CE_CONT, "!chain %d, sample %d: "
+ "temp=%d gain=%d power=%d pa_det=%d", c, j,
+ chans[1].samples[c][j].temp,
+ chans[1].samples[c][j].gain,
+ chans[1].samples[c][j].power,
+ chans[1].samples[c][j].pa_det);
+ }
+ }
+}
+#endif
+
+static void
+iwn5000_read_eeprom(struct iwn_softc *sc)
+{
+ struct iwn5000_eeprom_calib_hdr hdr;
+ int32_t volt;
+ uint32_t base, addr;
+ uint16_t val;
+ int i;
+
+ /* Read regulatory domain (4 ASCII characters). */
+ iwn_read_prom_data(sc, IWN5000_EEPROM_REG, &val, 2);
+ base = le16toh(val);
+ iwn_read_prom_data(sc, base + IWN5000_EEPROM_DOMAIN,
+ sc->eeprom_domain, 4);
+
+ /* Read the list of authorized channels (20MHz ones only). */
+ for (i = 0; i < 5; i++) {
+ addr = base + iwn5000_regulatory_bands[i];
+ iwn_read_eeprom_channels(sc, i, addr);
+ }
+
+ /* Read enhanced TX power information for 6000 Series. */
+ if (sc->hw_type >= IWN_HW_REV_TYPE_6000)
+ iwn_read_eeprom_enhinfo(sc);
+
+ iwn_read_prom_data(sc, IWN5000_EEPROM_CAL, &val, 2);
+ base = le16toh(val);
+ iwn_read_prom_data(sc, base, &hdr, sizeof hdr);
+ IWN_DBG("calib version=%u pa type=%u voltage=%u",
+ hdr.version, hdr.pa_type, le16toh(hdr.volt));
+ sc->calib_ver = hdr.version;
+
+ if (sc->hw_type == IWN_HW_REV_TYPE_2030 ||
+ sc->hw_type == IWN_HW_REV_TYPE_2000 ||
+ sc->hw_type == IWN_HW_REV_TYPE_135 ||
+ sc->hw_type == IWN_HW_REV_TYPE_105) {
+ sc->eeprom_voltage = le16toh(hdr.volt);
+ iwn_read_prom_data(sc, base + IWN5000_EEPROM_TEMP, &val, 2);
+ sc->eeprom_temp = le16toh(val);
+ iwn_read_prom_data(sc, base + IWN2000_EEPROM_RAWTEMP, &val, 2);
+ sc->eeprom_rawtemp = le16toh(val);
+ }
+
+ if (sc->hw_type == IWN_HW_REV_TYPE_5150) {
+ /* Compute temperature offset. */
+ iwn_read_prom_data(sc, base + IWN5000_EEPROM_TEMP, &val, 2);
+ sc->eeprom_temp = le16toh(val);
+ iwn_read_prom_data(sc, base + IWN5000_EEPROM_VOLT, &val, 2);
+ volt = le16toh(val);
+ sc->temp_off = sc->eeprom_temp - (volt / -5);
+ IWN_DBG("temp=%d volt=%d offset=%dK",
+ sc->eeprom_temp, volt, sc->temp_off);
+ } else {
+ /* Read crystal calibration. */
+ iwn_read_prom_data(sc, base + IWN5000_EEPROM_CRYSTAL,
+ &sc->eeprom_crystal, sizeof (uint32_t));
+ IWN_DBG("crystal calibration 0x%08x",
+ le32toh(sc->eeprom_crystal));
+ }
+}
+
+static void
+iwn_read_eeprom_channels(struct iwn_softc *sc, int n, uint32_t addr)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ const struct iwn_chan_band *band = &iwn_bands[n];
+ struct iwn_eeprom_chan channels[IWN_MAX_CHAN_PER_BAND];
+ uint8_t chan;
+ int i;
+
+ iwn_read_prom_data(sc, addr, channels,
+ band->nchan * sizeof (struct iwn_eeprom_chan));
+
+ for (i = 0; i < band->nchan; i++) {
+ if (!(channels[i].flags & IWN_EEPROM_CHAN_VALID))
+ continue;
+
+ chan = band->chan[i];
+
+ if (n == 0) { /* 2GHz band */
+ ic->ic_sup_channels[chan].ich_freq =
+ ieee80211_ieee2mhz(chan, IEEE80211_CHAN_2GHZ);
+ ic->ic_sup_channels[chan].ich_flags =
+ IEEE80211_CHAN_CCK | IEEE80211_CHAN_OFDM |
+ IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ;
+
+ } else { /* 5GHz band */
+ /*
+ * Some adapters support channels 7, 8, 11 and 12
+ * both in the 2GHz and 4.9GHz bands.
+ * Because of limitations in our net80211 layer,
+ * we don't support them in the 4.9GHz band.
+ */
+ if (chan <= 14)
+ continue;
+
+ ic->ic_sup_channels[chan].ich_freq =
+ ieee80211_ieee2mhz(chan, IEEE80211_CHAN_5GHZ);
+ ic->ic_sup_channels[chan].ich_flags =
+ IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_OFDM;
+ /* We have at least one valid 5GHz channel. */
+ sc->sc_flags |= IWN_FLAG_HAS_5GHZ;
+ }
+
+ /* Is active scan allowed on this channel? */
+ if (!(channels[i].flags & IWN_EEPROM_CHAN_ACTIVE)) {
+ ic->ic_sup_channels[chan].ich_flags |=
+ IEEE80211_CHAN_PASSIVE;
+ }
+
+ /* Save maximum allowed TX power for this channel. */
+ sc->maxpwr[chan] = channels[i].maxpwr;
+
+ IWN_DBG("adding chan %d flags=0x%x maxpwr=%d",
+ chan, channels[i].flags, sc->maxpwr[chan]);
+ }
+}
+
+static void
+iwn_read_eeprom_enhinfo(struct iwn_softc *sc)
+{
+ struct iwn_eeprom_enhinfo enhinfo[35];
+ uint16_t val, base;
+ int8_t maxpwr;
+ int i;
+
+ iwn_read_prom_data(sc, IWN5000_EEPROM_REG, &val, 2);
+ base = le16toh(val);
+ iwn_read_prom_data(sc, base + IWN6000_EEPROM_ENHINFO,
+ enhinfo, sizeof enhinfo);
+
+ memset(sc->enh_maxpwr, 0, sizeof sc->enh_maxpwr);
+ for (i = 0; i < __arraycount(enhinfo); i++) {
+ if (enhinfo[i].chan == 0 || enhinfo[i].reserved != 0)
+ continue; /* Skip invalid entries. */
+
+ maxpwr = 0;
+ if (sc->txchainmask & IWN_ANT_A)
+ maxpwr = MAX(maxpwr, enhinfo[i].chain[0]);
+ if (sc->txchainmask & IWN_ANT_B)
+ maxpwr = MAX(maxpwr, enhinfo[i].chain[1]);
+ if (sc->txchainmask & IWN_ANT_C)
+ maxpwr = MAX(maxpwr, enhinfo[i].chain[2]);
+ if (sc->ntxchains == 2)
+ maxpwr = MAX(maxpwr, enhinfo[i].mimo2);
+ else if (sc->ntxchains == 3)
+ maxpwr = MAX(maxpwr, enhinfo[i].mimo3);
+ maxpwr /= 2; /* Convert half-dBm to dBm. */
+
+ IWN_DBG("enhinfo %d, maxpwr=%d", i, maxpwr);
+ sc->enh_maxpwr[i] = maxpwr;
+ }
+}
+
+static struct ieee80211_node *
+iwn_node_alloc(ieee80211com_t *ic)
+{
+ _NOTE(ARGUNUSED(ic));
+ return (kmem_zalloc(sizeof (struct iwn_node), KM_NOSLEEP));
+}
+
+static void
+iwn_node_free(ieee80211_node_t *in)
+{
+ ASSERT(in != NULL);
+ ASSERT(in->in_ic != NULL);
+
+ if (in->in_wpa_ie != NULL)
+ ieee80211_free(in->in_wpa_ie);
+
+ if (in->in_wme_ie != NULL)
+ ieee80211_free(in->in_wme_ie);
+
+ if (in->in_htcap_ie != NULL)
+ ieee80211_free(in->in_htcap_ie);
+
+ kmem_free(in, sizeof (struct iwn_node));
+}
+
+static void
+iwn_newassoc(struct ieee80211_node *ni, int isnew)
+{
+ _NOTE(ARGUNUSED(isnew));
+ struct iwn_softc *sc = (struct iwn_softc *)&ni->in_ic;
+ struct iwn_node *wn = (void *)ni;
+ uint8_t rate, ridx;
+ int i;
+
+ ieee80211_amrr_node_init(&sc->amrr, &wn->amn);
+ /*
+ * Select a medium rate and depend on AMRR to raise/lower it.
+ */
+ ni->in_txrate = ni->in_rates.ir_nrates / 2;
+
+ for (i = 0; i < ni->in_rates.ir_nrates; i++) {
+ rate = ni->in_rates.ir_rates[i] & IEEE80211_RATE_VAL;
+ /* Map 802.11 rate to HW rate index. */
+ for (ridx = 0; ridx <= IWN_RIDX_MAX; ridx++)
+ if (iwn_rates[ridx].rate == rate)
+ break;
+ wn->ridx[i] = ridx;
+ }
+}
+
+static int
+iwn_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
+{
+ struct iwn_softc *sc = (struct iwn_softc *)ic;
+ enum ieee80211_state ostate;
+ int error;
+
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags |= IWN_FLAG_STOP_CALIB_TO;
+ mutex_exit(&sc->sc_mtx);
+
+ (void) untimeout(sc->calib_to);
+ sc->calib_to = 0;
+
+ mutex_enter(&sc->sc_mtx);
+ ostate = ic->ic_state;
+
+ DTRACE_PROBE5(new__state, int, sc->sc_flags,
+ enum ieee80211_state, ostate,
+ const char *, ieee80211_state_name[ostate],
+ enum ieee80211_state, nstate,
+ const char *, ieee80211_state_name[nstate]);
+
+ if ((sc->sc_flags & IWN_FLAG_RADIO_OFF) && nstate != IEEE80211_S_INIT) {
+ mutex_exit(&sc->sc_mtx);
+ return (IWN_FAIL);
+ }
+
+ if (!(sc->sc_flags & IWN_FLAG_HW_INITED) &&
+ nstate != IEEE80211_S_INIT) {
+ mutex_exit(&sc->sc_mtx);
+ return (IWN_FAIL);
+ }
+
+ switch (nstate) {
+ case IEEE80211_S_SCAN:
+ /* XXX Do not abort a running scan. */
+ if (sc->sc_flags & IWN_FLAG_SCANNING) {
+ if (ostate != nstate)
+ dev_err(sc->sc_dip, CE_WARN, "!scan request(%d)"
+ " while scanning(%d) ignored", nstate,
+ ostate);
+ mutex_exit(&sc->sc_mtx);
+ return (0);
+ }
+
+ bcopy(&sc->rxon, &sc->rxon_save, sizeof (sc->rxon));
+ sc->sc_ostate = ostate;
+
+ /* XXX Not sure if call and flags are needed. */
+ ieee80211_node_table_reset(&ic->ic_scan);
+ ic->ic_flags |= IEEE80211_F_SCAN | IEEE80211_F_ASCAN;
+ sc->sc_flags |= IWN_FLAG_SCANNING_2GHZ;
+
+ /* Make the link LED blink while we're scanning. */
+ iwn_set_led(sc, IWN_LED_LINK, 10, 10);
+
+ ic->ic_state = nstate;
+
+ error = iwn_scan(sc, IEEE80211_CHAN_2GHZ);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not initiate scan");
+ sc->sc_flags &= ~IWN_FLAG_SCANNING;
+ mutex_exit(&sc->sc_mtx);
+ return (error);
+ }
+
+ mutex_exit(&sc->sc_mtx);
+ sc->scan_to = timeout(iwn_abort_scan, sc, iwn_scan_timeout *
+ drv_usectohz(MICROSEC));
+ return (error);
+
+ case IEEE80211_S_ASSOC:
+ if (ostate != IEEE80211_S_RUN) {
+ mutex_exit(&sc->sc_mtx);
+ break;
+ }
+ /* FALLTHROUGH */
+ case IEEE80211_S_AUTH:
+ /* Reset state to handle reassociations correctly. */
+ sc->rxon.associd = 0;
+ sc->rxon.filter &= ~htole32(IWN_FILTER_BSS);
+ sc->calib.state = IWN_CALIB_STATE_INIT;
+
+ if ((error = iwn_auth(sc)) != 0) {
+ mutex_exit(&sc->sc_mtx);
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not move to auth state");
+ return error;
+ }
+ mutex_exit(&sc->sc_mtx);
+ break;
+
+ case IEEE80211_S_RUN:
+ if ((error = iwn_run(sc)) != 0) {
+ mutex_exit(&sc->sc_mtx);
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not move to run state");
+ return error;
+ }
+ mutex_exit(&sc->sc_mtx);
+ break;
+
+ case IEEE80211_S_INIT:
+ sc->sc_flags &= ~IWN_FLAG_SCANNING;
+ sc->calib.state = IWN_CALIB_STATE_INIT;
+
+ /*
+ * set LED off after init
+ */
+ iwn_set_led(sc, IWN_LED_LINK, 1, 0);
+
+ cv_signal(&sc->sc_scan_cv);
+ mutex_exit(&sc->sc_mtx);
+ if (sc->scan_to != 0)
+ (void) untimeout(sc->scan_to);
+ sc->scan_to = 0;
+ break;
+ }
+
+ error = sc->sc_newstate(ic, nstate, arg);
+
+ if (nstate == IEEE80211_S_RUN)
+ ieee80211_start_watchdog(ic, 1);
+
+ return (error);
+}
+
+static void
+iwn_iter_func(void *arg, struct ieee80211_node *ni)
+{
+ struct iwn_softc *sc = arg;
+ struct iwn_node *wn = (struct iwn_node *)ni;
+
+ ieee80211_amrr_choose(&sc->amrr, ni, &wn->amn);
+}
+
+static void
+iwn_calib_timeout(void *arg)
+{
+ struct iwn_softc *sc = arg;
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ mutex_enter(&sc->sc_mtx);
+
+ if (ic->ic_fixed_rate == IEEE80211_FIXED_RATE_NONE) {
+ if (ic->ic_opmode == IEEE80211_M_STA)
+ iwn_iter_func(sc, ic->ic_bss);
+ else
+ ieee80211_iterate_nodes(&ic->ic_sta, iwn_iter_func, sc);
+ }
+ /* Force automatic TX power calibration every 60 secs. */
+ if (++sc->calib_cnt >= 120) {
+ uint32_t flags = 0;
+
+ DTRACE_PROBE(get__statistics);
+ (void)iwn_cmd(sc, IWN_CMD_GET_STATISTICS, &flags,
+ sizeof flags, 1);
+ sc->calib_cnt = 0;
+ }
+
+ /* Automatic rate control triggered every 500ms. */
+ if ((sc->sc_flags & IWN_FLAG_STOP_CALIB_TO) == 0)
+ sc->calib_to = timeout(iwn_calib_timeout, sc,
+ drv_usectohz(500000));
+
+ mutex_exit(&sc->sc_mtx);
+}
+
+/*
+ * Process an RX_PHY firmware notification. This is usually immediately
+ * followed by an MPDU_RX_DONE notification.
+ */
+static void
+iwn_rx_phy(struct iwn_softc *sc, struct iwn_rx_desc *desc,
+ struct iwn_rx_data *data)
+{
+ struct iwn_rx_stat *stat = (struct iwn_rx_stat *)(desc + 1);
+
+ (void) ddi_dma_sync(data->dma_data.dma_hdl, sizeof (*desc),
+ sizeof (*stat), DDI_DMA_SYNC_FORKERNEL);
+
+ DTRACE_PROBE1(rx__phy, struct iwn_rx_stat *, stat);
+
+ /* Save RX statistics, they will be used on MPDU_RX_DONE. */
+ memcpy(&sc->last_rx_stat, stat, sizeof (*stat));
+ sc->last_rx_valid = 1;
+}
+
+/*
+ * Process an RX_DONE (4965AGN only) or MPDU_RX_DONE firmware notification.
+ * Each MPDU_RX_DONE notification must be preceded by an RX_PHY one.
+ */
+static void
+iwn_rx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc,
+ struct iwn_rx_data *data)
+{
+ struct iwn_ops *ops = &sc->ops;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct iwn_rx_ring *ring = &sc->rxq;
+ struct ieee80211_frame *wh;
+ struct ieee80211_node *ni;
+ mblk_t *m;
+ struct iwn_rx_stat *stat;
+ char *head;
+ uint32_t flags;
+ int len, rssi;
+
+ if (desc->type == IWN_MPDU_RX_DONE) {
+ /* Check for prior RX_PHY notification. */
+ if (!sc->last_rx_valid) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "missing RX_PHY");
+ return;
+ }
+ sc->last_rx_valid = 0;
+ stat = &sc->last_rx_stat;
+ } else
+ stat = (struct iwn_rx_stat *)(desc + 1);
+
+ (void) ddi_dma_sync(data->dma_data.dma_hdl, 0, 0,
+ DDI_DMA_SYNC_FORKERNEL);
+
+ if (stat->cfg_phy_len > IWN_STAT_MAXLEN) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!invalid RX statistic header");
+ return;
+ }
+ if (desc->type == IWN_MPDU_RX_DONE) {
+ struct iwn_rx_mpdu *mpdu = (struct iwn_rx_mpdu *)(desc + 1);
+ head = (char *)(mpdu + 1);
+ len = le16toh(mpdu->len);
+ } else {
+ head = (char *)(stat + 1) + stat->cfg_phy_len;
+ len = le16toh(stat->len);
+ }
+ /*LINTED: E_PTR_BAD_CAST_ALIGN*/
+ flags = le32toh(*(uint32_t *)(head + len));
+
+ /* Discard frames with a bad FCS early. */
+ if ((flags & IWN_RX_NOERROR) != IWN_RX_NOERROR) {
+ sc->sc_rx_err++;
+ ic->ic_stats.is_fcs_errors++;
+ return;
+ }
+ /* Discard frames that are too short. */
+ if (len < sizeof (*wh)) {
+ sc->sc_rx_err++;
+ return;
+ }
+
+ m = allocb(len, BPRI_MED);
+ if (m == NULL) {
+ sc->sc_rx_nobuf++;
+ return;
+ }
+
+ /* Update RX descriptor. */
+ ring->desc[ring->cur] =
+ htole32(data->dma_data.paddr >> 8);
+ (void) ddi_dma_sync(ring->desc_dma.dma_hdl,
+ ring->cur * sizeof (uint32_t), sizeof (uint32_t),
+ DDI_DMA_SYNC_FORDEV);
+
+ /* Grab a reference to the source node. */
+ wh = (struct ieee80211_frame*)head;
+ ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame *)wh);
+
+ /* XXX OpenBSD adds decryption here (see also comments in iwn_tx). */
+ /* NetBSD does decryption in ieee80211_input. */
+
+ rssi = ops->get_rssi(stat);
+
+ /*
+ * convert dBm to percentage
+ */
+ rssi = (100 * 75 * 75 - (-20 - rssi) * (15 * 75 + 62 * (-20 - rssi)))
+ / (75 * 75);
+ if (rssi > 100)
+ rssi = 100;
+ else if (rssi < 1)
+ rssi = 1;
+
+ bcopy(wh, m->b_wptr, len);
+ m->b_wptr += len;
+
+ /* XXX Added for NetBSD: scans never stop without it */
+ if (ic->ic_state == IEEE80211_S_SCAN)
+ iwn_fix_channel(sc, m, stat);
+
+ /* Send the frame to the 802.11 layer. */
+ ieee80211_input(ic, m, ni, rssi, 0);
+
+ /* Node is no longer needed. */
+ ieee80211_free_node(ni);
+}
+
+#ifndef IEEE80211_NO_HT
+/* Process an incoming Compressed BlockAck. */
+static void
+iwn_rx_compressed_ba(struct iwn_softc *sc, struct iwn_rx_desc *desc,
+ struct iwn_rx_data *data)
+{
+ struct iwn_compressed_ba *ba = (struct iwn_compressed_ba *)(desc + 1);
+ struct iwn_tx_ring *txq;
+
+ (void) ddi_dma_sync(data->dma_data.dma_hdl, sizeof (*desc),
+ sizeof (*ba), DDI_DMA_SYNC_FORKERNEL);
+
+ txq = &sc->txq[le16toh(ba->qid)];
+ /* XXX TBD */
+}
+#endif
+
+/*
+ * Process a CALIBRATION_RESULT notification sent by the initialization
+ * firmware on response to a CMD_CALIB_CONFIG command (5000 only).
+ */
+static void
+iwn5000_rx_calib_results(struct iwn_softc *sc, struct iwn_rx_desc *desc,
+ struct iwn_rx_data *data)
+{
+ struct iwn_phy_calib *calib = (struct iwn_phy_calib *)(desc + 1);
+ int len, idx = -1;
+
+ /* Runtime firmware should not send such a notification. */
+ if (sc->sc_flags & IWN_FLAG_CALIB_DONE)
+ return;
+
+ len = (le32toh(desc->len) & 0x3fff) - 4;
+ (void) ddi_dma_sync(data->dma_data.dma_hdl, sizeof (*desc), len,
+ DDI_DMA_SYNC_FORKERNEL);
+
+ switch (calib->code) {
+ case IWN5000_PHY_CALIB_DC:
+ if (sc->hw_type == IWN_HW_REV_TYPE_5150 ||
+ sc->hw_type == IWN_HW_REV_TYPE_2030 ||
+ sc->hw_type == IWN_HW_REV_TYPE_2000 ||
+ sc->hw_type == IWN_HW_REV_TYPE_135 ||
+ sc->hw_type == IWN_HW_REV_TYPE_105)
+ idx = 0;
+ break;
+ case IWN5000_PHY_CALIB_LO:
+ idx = 1;
+ break;
+ case IWN5000_PHY_CALIB_TX_IQ:
+ idx = 2;
+ break;
+ case IWN5000_PHY_CALIB_TX_IQ_PERIODIC:
+ if (sc->hw_type < IWN_HW_REV_TYPE_6000 &&
+ sc->hw_type != IWN_HW_REV_TYPE_5150)
+ idx = 3;
+ break;
+ case IWN5000_PHY_CALIB_BASE_BAND:
+ idx = 4;
+ break;
+ }
+ if (idx == -1) /* Ignore other results. */
+ return;
+
+ /* Save calibration result. */
+ if (sc->calibcmd[idx].buf != NULL)
+ kmem_free(sc->calibcmd[idx].buf, sc->calibcmd[idx].len);
+ sc->calibcmd[idx].buf = kmem_zalloc(len, KM_NOSLEEP);
+ if (sc->calibcmd[idx].buf == NULL) {
+ return;
+ }
+ sc->calibcmd[idx].len = len;
+ memcpy(sc->calibcmd[idx].buf, calib, len);
+}
+
+/*
+ * Process an RX_STATISTICS or BEACON_STATISTICS firmware notification.
+ * The latter is sent by the firmware after each received beacon.
+ */
+static void
+iwn_rx_statistics(struct iwn_softc *sc, struct iwn_rx_desc *desc,
+ struct iwn_rx_data *data)
+{
+ struct iwn_ops *ops = &sc->ops;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct iwn_calib_state *calib = &sc->calib;
+ struct iwn_stats *stats = (struct iwn_stats *)(desc + 1);
+ int temp = 0;
+
+ /* Ignore statistics received during a scan. */
+ if (ic->ic_state != IEEE80211_S_RUN)
+ return;
+
+ (void) ddi_dma_sync(data->dma_data.dma_hdl, sizeof (*desc),
+ sizeof (*stats), DDI_DMA_SYNC_FORKERNEL);
+
+ sc->calib_cnt = 0; /* Reset TX power calibration timeout. */
+
+ /* Test if temperature has changed. */
+ if (stats->general.temp != sc->rawtemp) {
+ /* Convert "raw" temperature to degC. */
+ sc->rawtemp = stats->general.temp;
+ temp = ops->get_temperature(sc);
+ sc->sc_misc->temp.value.ul = temp;
+
+ /* Update TX power if need be (4965AGN only). */
+ if (sc->hw_type == IWN_HW_REV_TYPE_4965)
+ iwn4965_power_calibration(sc, temp);
+ }
+
+ DTRACE_PROBE2(rx__statistics, struct iwn_stats *, stats, int, temp);
+
+ if (desc->type != IWN_BEACON_STATISTICS)
+ return; /* Reply to a statistics request. */
+
+ sc->noise = iwn_get_noise(&stats->rx.general);
+ sc->sc_misc->noise.value.l = sc->noise;
+
+ /* Test that RSSI and noise are present in stats report. */
+ if (le32toh(stats->rx.general.flags) != 1) {
+ return;
+ }
+
+ /*
+ * XXX Differential gain calibration makes the 6005 firmware
+ * crap out, so skip it for now. This effectively disables
+ * sensitivity tuning as well.
+ */
+ if (sc->hw_type == IWN_HW_REV_TYPE_6005)
+ return;
+
+ if (calib->state == IWN_CALIB_STATE_ASSOC)
+ iwn_collect_noise(sc, &stats->rx.general);
+ else if (calib->state == IWN_CALIB_STATE_RUN)
+ iwn_tune_sensitivity(sc, &stats->rx);
+}
+
+/*
+ * Process a TX_DONE firmware notification. Unfortunately, the 4965AGN
+ * and 5000 adapters have different incompatible TX status formats.
+ */
+static void
+iwn4965_tx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc,
+ struct iwn_rx_data *data)
+{
+ struct iwn4965_tx_stat *stat = (struct iwn4965_tx_stat *)(desc + 1);
+
+ (void) ddi_dma_sync(data->dma_data.dma_hdl, sizeof (*desc),
+ sizeof (*stat), DDI_DMA_SYNC_FORKERNEL);
+ iwn_tx_done(sc, desc, stat->ackfailcnt, le32toh(stat->status) & 0xff);
+}
+
+static void
+iwn5000_tx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc,
+ struct iwn_rx_data *data)
+{
+ struct iwn5000_tx_stat *stat = (struct iwn5000_tx_stat *)(desc + 1);
+
+#ifdef notyet
+ /* Reset TX scheduler slot. */
+ iwn5000_reset_sched(sc, desc->qid & 0xf, desc->idx);
+#endif
+
+ (void) ddi_dma_sync(data->dma_data.dma_hdl, sizeof (*desc),
+ sizeof (*stat), DDI_DMA_SYNC_FORKERNEL);
+ iwn_tx_done(sc, desc, stat->ackfailcnt, le16toh(stat->status) & 0xff);
+}
+
+/*
+ * Adapter-independent backend for TX_DONE firmware notifications.
+ */
+static void
+iwn_tx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc, int ackfailcnt,
+ uint8_t status)
+{
+ struct iwn_tx_ring *ring = &sc->txq[desc->qid & 0xf];
+ struct iwn_tx_data *data = &ring->data[desc->idx];
+ struct iwn_node *wn = (struct iwn_node *)data->ni;
+
+ /* Update rate control statistics. */
+ wn->amn.amn_txcnt++;
+ if (ackfailcnt > 0)
+ wn->amn.amn_retrycnt++;
+
+ if (status != 1 && status != 2)
+ sc->sc_tx_err++;
+ else
+ sc->sc_ic.ic_stats.is_tx_frags++;
+
+ ieee80211_free_node(data->ni);
+ data->ni = NULL;
+
+ mutex_enter(&sc->sc_tx_mtx);
+ sc->sc_tx_timer = 0;
+ if (--ring->queued < IWN_TX_RING_LOMARK) {
+ sc->qfullmsk &= ~(1 << ring->qid);
+ }
+ mac_tx_update(sc->sc_ic.ic_mach);
+ mutex_exit(&sc->sc_tx_mtx);
+}
+
+/*
+ * Process a "command done" firmware notification. This is where we wakeup
+ * processes waiting for a synchronous command completion.
+ */
+static void
+iwn_cmd_done(struct iwn_softc *sc, struct iwn_rx_desc *desc)
+{
+ struct iwn_tx_ring *ring = &sc->txq[IWN_CMD_QUEUE_NUM];
+ struct iwn_tx_data *data;
+
+ if ((desc->qid & 0xf) != IWN_CMD_QUEUE_NUM)
+ return; /* Not a command ack. */
+
+ data = &ring->data[desc->idx];
+
+ (void) ddi_dma_sync(data->dma_data.dma_hdl, 0, 0, DDI_DMA_SYNC_FORDEV);
+
+ /* If the command was mapped in an extra buffer, free it. */
+ if (data->cmd_dma.dma_hdl) {
+ (void) ddi_dma_sync(data->cmd_dma.dma_hdl, 0, 0,
+ DDI_DMA_SYNC_FORDEV);
+ iwn_dma_contig_free(&data->cmd_dma);
+ }
+
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_cmd_flag = SC_CMD_FLG_DONE;
+ cv_signal(&sc->sc_cmd_cv);
+ mutex_exit(&sc->sc_mtx);
+}
+
+/*
+ * Process an INT_FH_RX or INT_SW_RX interrupt.
+ */
+static void
+iwn_notif_intr(struct iwn_softc *sc)
+{
+ struct iwn_ops *ops = &sc->ops;
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint16_t hw;
+
+ ASSERT(sc != NULL);
+
+ (void) ddi_dma_sync(sc->rxq.stat_dma.dma_hdl, 0, 0,
+ DDI_DMA_SYNC_FORKERNEL);
+
+ hw = le16toh(sc->rxq.stat->closed_count) & 0xfff;
+ while (sc->rxq.cur != hw) {
+ struct iwn_rx_data *data = &sc->rxq.data[sc->rxq.cur];
+ struct iwn_rx_desc *desc;
+
+ (void) ddi_dma_sync(data->dma_data.dma_hdl, 0, sizeof (*desc),
+ DDI_DMA_SYNC_FORKERNEL);
+ desc = (struct iwn_rx_desc *)data->dma_data.vaddr;
+
+ DTRACE_PROBE1(notification__intr, struct iwn_rx_desc *, desc);
+
+ if (!(desc->qid & 0x80)) /* Reply to a command. */
+ iwn_cmd_done(sc, desc);
+
+ switch (desc->type) {
+ case IWN_RX_PHY:
+ iwn_rx_phy(sc, desc, data);
+ break;
+
+ case IWN_RX_DONE: /* 4965AGN only. */
+ case IWN_MPDU_RX_DONE:
+ /* An 802.11 frame has been received. */
+ iwn_rx_done(sc, desc, data);
+ break;
+#ifndef IEEE80211_NO_HT
+ case IWN_RX_COMPRESSED_BA:
+ /* A Compressed BlockAck has been received. */
+ iwn_rx_compressed_ba(sc, desc, data);
+ break;
+#endif
+ case IWN_TX_DONE:
+ /* An 802.11 frame has been transmitted. */
+ ops->tx_done(sc, desc, data);
+ break;
+
+ case IWN_RX_STATISTICS:
+ case IWN_BEACON_STATISTICS:
+ mutex_enter(&sc->sc_mtx);
+ iwn_rx_statistics(sc, desc, data);
+ mutex_exit(&sc->sc_mtx);
+ break;
+
+ case IWN_BEACON_MISSED:
+ {
+ struct iwn_beacon_missed *miss =
+ (struct iwn_beacon_missed *)(desc + 1);
+
+ (void) ddi_dma_sync(data->dma_data.dma_hdl,
+ sizeof (*desc), sizeof (*miss),
+ DDI_DMA_SYNC_FORKERNEL);
+ /*
+ * If more than iwn_beacons_missed_disconnect
+ * consecutive beacons are missed, we've probably lost
+ * our connection.
+ * If more than iwn_beacons_missed_sensitivity
+ * consecutive beacons are missed, reinitialize the
+ * sensitivity state machine.
+ */
+ DTRACE_PROBE1(beacons__missed,
+ struct iwn_beacon_missed *, miss);
+ if (ic->ic_state == IEEE80211_S_RUN) {
+ if (le32toh(miss->consecutive)
+ > iwn_beacons_missed_disconnect) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!iwn_notif_intr(): %d consecutive "
+ "beacons missed, disconnecting",
+ le32toh(miss->consecutive));
+ ieee80211_new_state(ic,
+ IEEE80211_S_INIT, -1);
+ } else if (le32toh(miss->consecutive)
+ > iwn_beacons_missed_sensitivity) {
+ mutex_enter(&sc->sc_mtx);
+ (void)iwn_init_sensitivity(sc);
+ mutex_exit(&sc->sc_mtx);
+ }
+ }
+ break;
+ }
+ case IWN_UC_READY:
+ {
+ struct iwn_ucode_info *uc =
+ (struct iwn_ucode_info *)(desc + 1);
+
+ /* The microcontroller is ready. */
+ (void) ddi_dma_sync(data->dma_data.dma_hdl,
+ sizeof (*desc), sizeof (*uc),
+ DDI_DMA_SYNC_FORKERNEL);
+ DTRACE_PROBE1(uc__ready, struct iwn_ucode_info *, uc)
+
+ if (le32toh(uc->valid) != 1) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!microcontroller initialization failed");
+ break;
+ }
+ if (uc->subtype == IWN_UCODE_INIT) {
+ /* Save microcontroller report. */
+ memcpy(&sc->ucode_info, uc, sizeof (*uc));
+ }
+ /* Save the address of the error log in SRAM. */
+ sc->errptr = le32toh(uc->errptr);
+ break;
+ }
+ case IWN_STATE_CHANGED:
+ {
+ /*LINTED: E_PTR_BAD_CAST_ALIGN*/
+ uint32_t *status = (uint32_t *)(desc + 1);
+
+ /* Enabled/disabled notification. */
+ (void) ddi_dma_sync(data->dma_data.dma_hdl,
+ sizeof (*desc), sizeof (*status),
+ DDI_DMA_SYNC_FORKERNEL);
+ DTRACE_PROBE1(state__changed, uint32_t, *status);
+
+ if (le32toh(*status) & 1) {
+ /* The radio button has to be pushed. */
+ dev_err(sc->sc_dip, CE_WARN,
+ "!Radio transmitter is off");
+ /* Turn the interface down. */
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags |=
+ IWN_FLAG_HW_ERR_RECOVER |
+ IWN_FLAG_RADIO_OFF;
+ mutex_exit(&sc->sc_mtx);
+ ieee80211_new_state(&sc->sc_ic,
+ IEEE80211_S_INIT, -1);
+
+ return; /* No further processing. */
+ }
+ break;
+ }
+ case IWN_START_SCAN:
+ {
+ struct iwn_start_scan *scan =
+ (struct iwn_start_scan *)(desc + 1);
+
+ (void) ddi_dma_sync(data->dma_data.dma_hdl,
+ sizeof (*desc), sizeof (*scan),
+ DDI_DMA_SYNC_FORKERNEL);
+ DTRACE_PROBE2(start__scan, uint8_t, scan->chan,
+ uint32_t, le32toh(scan->status));
+
+ /* Fix current channel. */
+ ic->ic_curchan = ic->ic_bss->in_chan =
+ &ic->ic_sup_channels[scan->chan];
+ break;
+ }
+ case IWN_STOP_SCAN:
+ {
+ struct iwn_stop_scan *scan =
+ (struct iwn_stop_scan *)(desc + 1);
+
+ (void) ddi_dma_sync(data->dma_data.dma_hdl,
+ sizeof (*desc), sizeof (*scan),
+ DDI_DMA_SYNC_FORKERNEL);
+ DTRACE_PROBE3(stop__scan, uint8_t, scan->chan,
+ uint32_t, le32toh(scan->status),
+ uint8_t, scan->nchan);
+
+ if (iwn_enable_5ghz != 0 &&
+ (sc->sc_flags & IWN_FLAG_SCANNING_2GHZ) &&
+ (sc->sc_flags & IWN_FLAG_HAS_5GHZ)) {
+ /*
+ * We just finished scanning 2GHz channels,
+ * start scanning 5GHz ones.
+ */
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags |= IWN_FLAG_SCANNING_5GHZ;
+ sc->sc_flags &= ~IWN_FLAG_SCANNING_2GHZ;
+ if (iwn_scan(sc, IEEE80211_CHAN_5GHZ) == 0) {
+ mutex_exit(&sc->sc_mtx);
+ break;
+ }
+ mutex_exit(&sc->sc_mtx);
+ }
+ ieee80211_end_scan(ic);
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags &= ~IWN_FLAG_SCANNING;
+ cv_signal(&sc->sc_scan_cv);
+ mutex_exit(&sc->sc_mtx);
+ (void) untimeout(sc->scan_to);
+ sc->scan_to = 0;
+ break;
+ }
+ case IWN5000_CALIBRATION_RESULT:
+ iwn5000_rx_calib_results(sc, desc, data);
+ break;
+
+ case IWN5000_CALIBRATION_DONE:
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags |= IWN_FLAG_CALIB_DONE;
+ cv_signal(&sc->sc_calib_cv);
+ mutex_exit(&sc->sc_mtx);
+ break;
+ }
+
+ sc->rxq.cur = (sc->rxq.cur + 1) % IWN_RX_RING_COUNT;
+ }
+
+ /* Tell the firmware what we have processed. */
+ hw = (hw == 0) ? IWN_RX_RING_COUNT - 1 : hw - 1;
+ IWN_WRITE(sc, IWN_FH_RX_WPTR, hw & ~7);
+}
+
+/*
+ * Process an INT_WAKEUP interrupt raised when the microcontroller wakes up
+ * from power-down sleep mode.
+ */
+static void
+iwn_wakeup_intr(struct iwn_softc *sc)
+{
+ int qid;
+
+ DTRACE_PROBE(wakeup__intr);
+
+ /* Wakeup RX and TX rings. */
+ IWN_WRITE(sc, IWN_FH_RX_WPTR, sc->rxq.cur & ~7);
+ for (qid = 0; qid < sc->ntxqs; qid++) {
+ struct iwn_tx_ring *ring = &sc->txq[qid];
+ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | ring->cur);
+ }
+}
+
+/*
+ * Dump the error log of the firmware when a firmware panic occurs. Although
+ * we can't debug the firmware because it is neither open source nor free, it
+ * can help us to identify certain classes of problems.
+ */
+static void
+iwn_fatal_intr(struct iwn_softc *sc)
+{
+ struct iwn_fw_dump dump;
+ int i;
+
+ /* Force a complete recalibration on next init. */
+ sc->sc_flags &= ~IWN_FLAG_CALIB_DONE;
+
+ /* Check that the error log address is valid. */
+ if (sc->errptr < IWN_FW_DATA_BASE ||
+ sc->errptr + sizeof (dump) >
+ IWN_FW_DATA_BASE + sc->fw_data_maxsz) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!bad firmware error log address 0x%08x", sc->errptr);
+ return;
+ }
+ if (iwn_nic_lock(sc) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not read firmware error log");
+ return;
+ }
+ /* Read firmware error log from SRAM. */
+ /*LINTED: E_PTR_BAD_CAST_ALIGN*/
+ iwn_mem_read_region_4(sc, sc->errptr, (uint32_t *)&dump,
+ sizeof (dump) / sizeof (uint32_t));
+ iwn_nic_unlock(sc);
+
+ if (dump.valid == 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!firmware error log is empty");
+ return;
+ }
+ dev_err(sc->sc_dip, CE_WARN, "!firmware error log:");
+ dev_err(sc->sc_dip, CE_CONT, "! error type = \"%s\" (0x%08X)",
+ (dump.id < __arraycount(iwn_fw_errmsg)) ?
+ iwn_fw_errmsg[dump.id] : "UNKNOWN",
+ dump.id);
+ dev_err(sc->sc_dip, CE_CONT, "! program counter = 0x%08X", dump.pc);
+ dev_err(sc->sc_dip, CE_CONT, "! source line = 0x%08X",
+ dump.src_line);
+ dev_err(sc->sc_dip, CE_CONT, "! error data = 0x%08X%08X",
+ dump.error_data[0], dump.error_data[1]);
+ dev_err(sc->sc_dip, CE_CONT, "! branch link = 0x%08X%08X",
+ dump.branch_link[0], dump.branch_link[1]);
+ dev_err(sc->sc_dip, CE_CONT, "! interrupt link = 0x%08X%08X",
+ dump.interrupt_link[0], dump.interrupt_link[1]);
+ dev_err(sc->sc_dip, CE_CONT, "! time = %u", dump.time[0]);
+
+ /* Dump driver status (TX and RX rings) while we're here. */
+ dev_err(sc->sc_dip, CE_WARN, "!driver status:");
+ for (i = 0; i < sc->ntxqs; i++) {
+ struct iwn_tx_ring *ring = &sc->txq[i];
+ dev_err(sc->sc_dip, CE_WARN,
+ "! tx ring %2d: qid=%2d cur=%3d queued=%3d",
+ i, ring->qid, ring->cur, ring->queued);
+ }
+ dev_err(sc->sc_dip, CE_WARN, "! rx ring: cur=%d", sc->rxq.cur);
+ dev_err(sc->sc_dip, CE_WARN, "! 802.11 state %d", sc->sc_ic.ic_state);
+}
+
+/*ARGSUSED1*/
+static uint_t
+iwn_intr(caddr_t arg, caddr_t unused)
+{
+ _NOTE(ARGUNUSED(unused));
+ /*LINTED: E_PTR_BAD_CAST_ALIGN*/
+ struct iwn_softc *sc = (struct iwn_softc *)arg;
+ uint32_t r1, r2, tmp;
+
+ if (sc == NULL)
+ return (DDI_INTR_UNCLAIMED);
+
+ /* Disable interrupts. */
+ IWN_WRITE(sc, IWN_INT_MASK, 0);
+
+ /* Read interrupts from ICT (fast) or from registers (slow). */
+ if (sc->sc_flags & IWN_FLAG_USE_ICT) {
+ (void) ddi_dma_sync(sc->ict_dma.dma_hdl, 0, 0,
+ DDI_DMA_SYNC_FORKERNEL);
+ tmp = 0;
+ while (sc->ict[sc->ict_cur] != 0) {
+ tmp |= sc->ict[sc->ict_cur];
+ sc->ict[sc->ict_cur] = 0; /* Acknowledge. */
+ sc->ict_cur = (sc->ict_cur + 1) % IWN_ICT_COUNT;
+ }
+ (void) ddi_dma_sync(sc->ict_dma.dma_hdl, 0, 0,
+ DDI_DMA_SYNC_FORDEV);
+ tmp = le32toh(tmp);
+ if (tmp == 0xffffffff) /* Shouldn't happen. */
+ tmp = 0;
+ else if (tmp & 0xc0000) /* Workaround a HW bug. */
+ tmp |= 0x8000;
+ r1 = (tmp & 0xff00) << 16 | (tmp & 0xff);
+ r2 = 0; /* Unused. */
+ } else {
+ r1 = IWN_READ(sc, IWN_INT);
+ if (r1 == 0xffffffff || (r1 & 0xfffffff0) == 0xa5a5a5a0)
+ return (DDI_INTR_UNCLAIMED); /* Hardware gone! */
+ r2 = IWN_READ(sc, IWN_FH_INT);
+ }
+ if (r1 == 0 && r2 == 0) {
+ IWN_WRITE(sc, IWN_INT_MASK, sc->int_mask);
+ return (DDI_INTR_UNCLAIMED); /* Interrupt not for us. */
+ }
+
+ /* Acknowledge interrupts. */
+ IWN_WRITE(sc, IWN_INT, r1);
+ if (!(sc->sc_flags & IWN_FLAG_USE_ICT))
+ IWN_WRITE(sc, IWN_FH_INT, r2);
+
+ if (r1 & IWN_INT_RF_TOGGLED) {
+ tmp = IWN_READ(sc, IWN_GP_CNTRL);
+ dev_err(sc->sc_dip, CE_NOTE,
+ "!RF switch: radio %s",
+ (tmp & IWN_GP_CNTRL_RFKILL) ? "enabled" : "disabled");
+ }
+ if (r1 & IWN_INT_CT_REACHED) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!critical temperature reached!");
+ }
+ if (r1 & (IWN_INT_SW_ERR | IWN_INT_HW_ERR)) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!fatal firmware error");
+ /* Dump firmware error log and stop. */
+ iwn_fatal_intr(sc);
+ iwn_hw_stop(sc, B_TRUE);
+ if (!IWN_CHK_FAST_RECOVER(sc))
+ ieee80211_new_state(&sc->sc_ic, IEEE80211_S_INIT, -1);
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags |= IWN_FLAG_HW_ERR_RECOVER;
+ mutex_exit(&sc->sc_mtx);
+
+ return (DDI_INTR_CLAIMED);
+ }
+ if ((r1 & (IWN_INT_FH_RX | IWN_INT_SW_RX | IWN_INT_RX_PERIODIC)) ||
+ (r2 & IWN_FH_INT_RX)) {
+ if (sc->sc_flags & IWN_FLAG_USE_ICT) {
+ int ena = (r1 & (IWN_INT_FH_RX | IWN_INT_SW_RX));
+
+ if (ena)
+ IWN_WRITE(sc, IWN_FH_INT, IWN_FH_INT_RX);
+ IWN_WRITE_1(sc, IWN_INT_PERIODIC,
+ IWN_INT_PERIODIC_DIS);
+ iwn_notif_intr(sc);
+ if (ena)
+ IWN_WRITE_1(sc, IWN_INT_PERIODIC,
+ IWN_INT_PERIODIC_ENA);
+ } else {
+ iwn_notif_intr(sc);
+ }
+ }
+
+ if ((r1 & IWN_INT_FH_TX) || (r2 & IWN_FH_INT_TX)) {
+ if (sc->sc_flags & IWN_FLAG_USE_ICT)
+ IWN_WRITE(sc, IWN_FH_INT, IWN_FH_INT_TX);
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags |= IWN_FLAG_FW_DMA;
+ cv_signal(&sc->sc_fhdma_cv);
+ mutex_exit(&sc->sc_mtx);
+ }
+
+ if (r1 & IWN_INT_ALIVE) {
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags |= IWN_FLAG_FW_ALIVE;
+ cv_signal(&sc->sc_alive_cv);
+ mutex_exit(&sc->sc_mtx);
+ }
+
+ if (r1 & IWN_INT_WAKEUP)
+ iwn_wakeup_intr(sc);
+
+ /* Re-enable interrupts. */
+ IWN_WRITE(sc, IWN_INT_MASK, sc->int_mask);
+ return (DDI_INTR_CLAIMED);
+}
+
+/*
+ * Update TX scheduler ring when transmitting an 802.11 frame (4965AGN and
+ * 5000 adapters use a slightly different format).
+ */
+static void
+iwn4965_update_sched(struct iwn_softc *sc, int qid, int idx, uint8_t id,
+ uint16_t len)
+{
+ _NOTE(ARGUNUSED(id));
+ int w_idx = qid * IWN4965_SCHED_COUNT + idx;
+ uint16_t *w = &sc->sched[w_idx];
+
+ *w = htole16(len + 8);
+ (void) ddi_dma_sync(sc->sched_dma.dma_hdl, w_idx * sizeof (uint16_t),
+ sizeof (uint16_t), DDI_DMA_SYNC_FORDEV);
+ if (idx < IWN_SCHED_WINSZ) {
+ *(w + IWN_TX_RING_COUNT) = *w;
+ (void) ddi_dma_sync(sc->sched_dma.dma_hdl,
+ (w_idx + IWN_TX_RING_COUNT) * sizeof (uint16_t),
+ sizeof (uint16_t), DDI_DMA_SYNC_FORDEV);
+ }
+}
+
+static void
+iwn5000_update_sched(struct iwn_softc *sc, int qid, int idx, uint8_t id,
+ uint16_t len)
+{
+ int w_idx = qid * IWN5000_SCHED_COUNT + idx;
+ uint16_t *w = &sc->sched[w_idx];
+
+ *w = htole16(id << 12 | (len + 8));
+ (void) ddi_dma_sync(sc->sched_dma.dma_hdl, w_idx * sizeof (uint16_t),
+ sizeof (uint16_t), DDI_DMA_SYNC_FORDEV);
+ if (idx < IWN_SCHED_WINSZ) {
+ *(w + IWN_TX_RING_COUNT) = *w;
+ (void) ddi_dma_sync(sc->sched_dma.dma_hdl,
+ (w_idx + IWN_TX_RING_COUNT) * sizeof (uint16_t),
+ sizeof (uint16_t), DDI_DMA_SYNC_FORDEV);
+ }
+}
+
+#ifdef notyet
+static void
+iwn5000_reset_sched(struct iwn_softc *sc, int qid, int idx)
+{
+ int w_idx = qid * IWN5000_SCHED_COUNT + idx;
+ uint16_t *w = &sc->sched[w_idx];
+
+ *w = (*w & htole16(0xf000)) | htole16(1);
+ (void) ddi_dma_sync(sc->sched_dma.dma_hdl, w_idx * sizeof (uint16_t),
+ sizeof (uint16_t), DDI_DMA_SYNC_FORDEV);
+ if (idx < IWN_SCHED_WINSZ) {
+ *(w + IWN_TX_RING_COUNT) = *w;
+ (void) ddi_dma_sync(sc->sched_dma.dma_hdl,
+ (w_idx + IWN_TX_RING_COUNT) * sizeof (uint16_t),
+ sizeof (uint16_t), DDI_DMA_SYNC_FORDEV);
+ }
+}
+#endif
+
+/*
+ * This function is only for compatibility with Net80211 module.
+ * iwn_qosparam_to_hw() is the actual function updating EDCA
+ * parameters to hardware.
+ */
+static int
+iwn_wme_update(struct ieee80211com *ic)
+{
+ _NOTE(ARGUNUSED(ic));
+ return (0);
+}
+
+static int
+iwn_wme_to_qos_ac(struct iwn_softc *sc, int wme_ac)
+{
+ int qos_ac;
+
+ switch (wme_ac) {
+ case WME_AC_BE:
+ qos_ac = QOS_AC_BK;
+ break;
+ case WME_AC_BK:
+ qos_ac = QOS_AC_BE;
+ break;
+ case WME_AC_VI:
+ qos_ac = QOS_AC_VI;
+ break;
+ case WME_AC_VO:
+ qos_ac = QOS_AC_VO;
+ break;
+ default:
+ dev_err(sc->sc_dip, CE_WARN, "!iwn_wme_to_qos_ac(): "
+ "WME AC index is not in suitable range.\n");
+ qos_ac = QOS_AC_INVALID;
+ break;
+ }
+
+ return (qos_ac);
+}
+
+static uint16_t
+iwn_cw_e_to_cw(uint8_t cw_e)
+{
+ uint16_t cw = 1;
+
+ while (cw_e > 0) {
+ cw <<= 1;
+ cw_e--;
+ }
+
+ cw -= 1;
+ return (cw);
+}
+
+static int
+iwn_wmeparam_check(struct iwn_softc *sc, struct wmeParams *wmeparam)
+{
+ int i;
+
+ for (i = 0; i < WME_NUM_AC; i++) {
+
+ if ((wmeparam[i].wmep_logcwmax > QOS_CW_RANGE_MAX) ||
+ (wmeparam[i].wmep_logcwmin >= wmeparam[i].wmep_logcwmax)) {
+ cmn_err(CE_WARN, "iwn_wmeparam_check(): "
+ "Contention window is not in suitable range.\n");
+ return (IWN_FAIL);
+ }
+
+ if ((wmeparam[i].wmep_aifsn < QOS_AIFSN_MIN) ||
+ (wmeparam[i].wmep_aifsn > QOS_AIFSN_MAX)) {
+ dev_err(sc->sc_dip, CE_WARN, "!iwn_wmeparam_check(): "
+ "Arbitration interframe space number"
+ "is not in suitable range.\n");
+ return (IWN_FAIL);
+ }
+ }
+
+ return (IWN_SUCCESS);
+}
+
+/*
+ * This function updates EDCA parameters into hardware.
+ * FIFO0-background, FIFO1-best effort, FIFO2-video, FIFO3-voice.
+ */
+static int
+iwn_qosparam_to_hw(struct iwn_softc *sc, int async)
+{
+ ieee80211com_t *ic = &sc->sc_ic;
+ ieee80211_node_t *in = ic->ic_bss;
+ struct wmeParams *wmeparam;
+ struct iwn_edca_params edcaparam;
+ int i, j;
+ int err = IWN_FAIL;
+
+ if ((in->in_flags & IEEE80211_NODE_QOS) &&
+ (IEEE80211_M_STA == ic->ic_opmode)) {
+ wmeparam = ic->ic_wme.wme_chanParams.cap_wmeParams;
+ } else {
+ return (IWN_SUCCESS);
+ }
+
+ (void) memset(&edcaparam, 0, sizeof (edcaparam));
+
+ err = iwn_wmeparam_check(sc, wmeparam);
+ if (err != IWN_SUCCESS) {
+ return (err);
+ }
+
+ if (in->in_flags & IEEE80211_NODE_QOS) {
+ edcaparam.flags |= QOS_PARAM_FLG_UPDATE_EDCA;
+ }
+
+ if (in->in_flags & (IEEE80211_NODE_QOS | IEEE80211_NODE_HT)) {
+ edcaparam.flags |= QOS_PARAM_FLG_TGN;
+ }
+
+ for (i = 0; i < WME_NUM_AC; i++) {
+
+ j = iwn_wme_to_qos_ac(sc, i);
+ if (j < QOS_AC_BK || j > QOS_AC_VO) {
+ return (IWN_FAIL);
+ }
+
+ sc->sc_edca->ac[j].cwmin.value.ul = edcaparam.ac[j].cwmin =
+ iwn_cw_e_to_cw(wmeparam[i].wmep_logcwmin);
+ sc->sc_edca->ac[j].cwmax.value.ul = edcaparam.ac[j].cwmax =
+ iwn_cw_e_to_cw(wmeparam[i].wmep_logcwmax);
+ sc->sc_edca->ac[j].aifsn.value.ul = edcaparam.ac[j].aifsn =
+ wmeparam[i].wmep_aifsn;
+ sc->sc_edca->ac[j].txop.value.ul = edcaparam.ac[j].txoplimit =
+ (uint16_t)(wmeparam[i].wmep_txopLimit * 32);
+ }
+
+ err = iwn_cmd(sc, IWN_CMD_EDCA_PARAMS, &edcaparam,
+ sizeof (edcaparam), async);
+ if (err != IWN_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN, "!iwn_qosparam_to_hw(): "
+ "failed to update QoS parameters into hardware.");
+ return (err);
+ }
+
+ return (err);
+}
+
+static inline int
+iwn_wme_tid_qos_ac(int tid)
+{
+ switch (tid) {
+ case 1:
+ case 2:
+ return (QOS_AC_BK);
+ case 0:
+ case 3:
+ return (QOS_AC_BE);
+ case 4:
+ case 5:
+ return (QOS_AC_VI);
+ case 6:
+ case 7:
+ return (QOS_AC_VO);
+ }
+
+ return (QOS_AC_BE);
+}
+
+static inline int
+iwn_qos_ac_to_txq(int qos_ac)
+{
+ switch (qos_ac) {
+ case QOS_AC_BK:
+ return (QOS_AC_BK_TO_TXQ);
+ case QOS_AC_BE:
+ return (QOS_AC_BE_TO_TXQ);
+ case QOS_AC_VI:
+ return (QOS_AC_VI_TO_TXQ);
+ case QOS_AC_VO:
+ return (QOS_AC_VO_TO_TXQ);
+ }
+
+ return (QOS_AC_BE_TO_TXQ);
+}
+
+static int
+iwn_wme_tid_to_txq(struct iwn_softc *sc, int tid)
+{
+ int queue_n = TXQ_FOR_AC_INVALID;
+ int qos_ac;
+
+ if (tid < WME_TID_MIN ||
+ tid > WME_TID_MAX) {
+ dev_err(sc->sc_dip, CE_WARN, "!wme_tid_to_txq(): "
+ "TID is not in suitable range.");
+ return (queue_n);
+ }
+
+ qos_ac = iwn_wme_tid_qos_ac(tid);
+ queue_n = iwn_qos_ac_to_txq(qos_ac);
+
+ return (queue_n);
+}
+
+static int
+iwn_send(ieee80211com_t *ic, mblk_t *mp, uint8_t type)
+{
+ struct iwn_softc *sc = (struct iwn_softc *)ic;
+ struct iwn_node *wn;
+ struct iwn_tx_ring *ring;
+ struct iwn_tx_desc *desc;
+ struct iwn_tx_data *data;
+ struct iwn_tx_cmd *cmd;
+ struct iwn_cmd_data *tx;
+ ieee80211_node_t *in;
+ const struct iwn_rate *rinfo;
+ struct ieee80211_frame *wh;
+ struct ieee80211_key *k = NULL;
+ uint32_t flags;
+ uint_t hdrlen;
+ uint8_t ridx, txant;
+ int i, totlen, seglen, pad;
+ int txq_id = NON_QOS_TXQ;
+ struct ieee80211_qosframe *qwh = NULL;
+ uint8_t tid = WME_TID_INVALID;
+ ddi_dma_cookie_t cookie;
+ mblk_t *m0, *m;
+ int mblen, off;
+
+ int noack = 0;
+
+ if (ic == NULL)
+ return (EIO);
+
+ if ((mp == NULL) || (MBLKL(mp) <= 0))
+ return (EIO);
+
+ if (sc->sc_flags & IWN_FLAG_SUSPEND) {
+ freemsg(mp);
+ sc->sc_tx_err++;
+ return(EIO);
+ }
+
+ wh = (struct ieee80211_frame *)mp->b_rptr;
+
+ hdrlen = ieee80211_hdrspace(ic, mp->b_rptr);
+
+ /*
+ * determine send which AP or station in IBSS
+ */
+ in = ieee80211_find_txnode(ic, wh->i_addr1);
+ if (in == NULL) {
+ dev_err(sc->sc_dip, CE_WARN, "!iwn_send(): "
+ "failed to find tx node");
+ freemsg(mp);
+ sc->sc_tx_err++;
+ return(EIO);
+ }
+
+ wn = (struct iwn_node *)in;
+
+ /*
+ * Determine TX queue according to traffic ID in frame
+ * if working in QoS mode.
+ */
+ if (in->in_flags & IEEE80211_NODE_QOS) {
+ if ((type & IEEE80211_FC0_TYPE_MASK) ==
+ IEEE80211_FC0_TYPE_DATA) {
+ if (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_QOS) {
+ qwh = (struct ieee80211_qosframe *)wh;
+
+ tid = qwh->i_qos[0] & IEEE80211_QOS_TID;
+ txq_id = iwn_wme_tid_to_txq(sc, tid);
+
+ if (txq_id < TXQ_FOR_AC_MIN ||
+ (txq_id > TXQ_FOR_AC_MAX)) {
+ freemsg(mp);
+ sc->sc_tx_err++;
+ return(EIO);
+ }
+ } else {
+ txq_id = NON_QOS_TXQ;
+ }
+ } else if ((type & IEEE80211_FC0_TYPE_MASK) ==
+ IEEE80211_FC0_TYPE_MGT) {
+ txq_id = QOS_TXQ_FOR_MGT;
+ } else {
+ txq_id = NON_QOS_TXQ;
+ }
+ } else {
+ txq_id = NON_QOS_TXQ;
+ }
+
+ if (sc->qfullmsk & (1 << txq_id)) {
+ sc->sc_tx_err++;
+ /* net80211-initiated send */
+ if ((type & IEEE80211_FC0_TYPE_MASK) !=
+ IEEE80211_FC0_TYPE_DATA)
+ freemsg(mp);
+ return (EAGAIN);
+ }
+
+ /* Choose a TX rate index. */
+ if (IEEE80211_IS_MULTICAST(wh->i_addr1) ||
+ type != IEEE80211_FC0_TYPE_DATA) {
+ ridx = (ic->ic_curmode == IEEE80211_MODE_11A) ?
+ IWN_RIDX_OFDM6 : IWN_RIDX_CCK1;
+ } else if (ic->ic_fixed_rate != IEEE80211_FIXED_RATE_NONE) {
+ ridx = sc->fixed_ridx;
+ } else
+ ridx = wn->ridx[in->in_txrate];
+ rinfo = &iwn_rates[ridx];
+
+ m = allocb(msgdsize(mp) + 32, BPRI_MED);
+ if (m) {
+ for (off = 0, m0 = mp; m0 != NULL; m0 = m0->b_cont) {
+ mblen = MBLKL(m0);
+ bcopy(m0->b_rptr, m->b_rptr + off, mblen);
+ off += mblen;
+ }
+
+ m->b_wptr += off;
+
+ freemsg(mp);
+ mp = m;
+
+ wh = (struct ieee80211_frame *)mp->b_rptr;
+ } else {
+ dev_err(sc->sc_dip, CE_WARN, "!iwn_send(): can't copy");
+ /* net80211-initiated send */
+ if ((type & IEEE80211_FC0_TYPE_MASK) !=
+ IEEE80211_FC0_TYPE_DATA)
+ freemsg(mp);
+ return (EAGAIN);
+ }
+
+
+ /*
+ * Net80211 module encapsulate outbound data frames.
+ * Add some fields of 80211 frame.
+ */
+ if ((type & IEEE80211_FC0_TYPE_MASK) ==
+ IEEE80211_FC0_TYPE_DATA)
+ (void) ieee80211_encap(ic, mp, in);
+
+ /* Encrypt the frame if need be. */
+ if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
+ k = ieee80211_crypto_encap(ic, mp);
+ if (k == NULL) {
+ freemsg(mp);
+ return(EIO);
+ }
+ /* Packet header may have moved, reset our local pointer. */
+ wh = (struct ieee80211_frame *)mp->b_rptr;
+ }
+ totlen = msgdsize(mp);
+
+ mutex_enter(&sc->sc_tx_mtx);
+ ring = &sc->txq[txq_id];
+ desc = &ring->desc[ring->cur];
+ data = &ring->data[ring->cur];
+
+ /* Prepare TX firmware command. */
+ cmd = &ring->cmd[ring->cur];
+ cmd->code = IWN_CMD_TX_DATA;
+ cmd->flags = 0;
+ cmd->qid = ring->qid;
+ cmd->idx = ring->cur;
+
+ tx = (struct iwn_cmd_data *)cmd->data;
+ /* NB: No need to clear tx, all fields are reinitialized here. */
+ tx->scratch = 0; /* clear "scratch" area */
+
+ flags = 0;
+ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
+ /* Unicast frame, check if an ACK is expected. */
+ if (!noack)
+ flags |= IWN_TX_NEED_ACK;
+ }
+
+ if ((wh->i_fc[0] &
+ (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) ==
+ (IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_BAR))
+ flags |= IWN_TX_IMM_BA; /* Cannot happen yet. */
+
+ ASSERT((flags & IWN_TX_IMM_BA) == 0);
+
+ if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG)
+ flags |= IWN_TX_MORE_FRAG; /* Cannot happen yet. */
+
+ ASSERT((flags & IWN_TX_MORE_FRAG) == 0);
+
+ /* Check if frame must be protected using RTS/CTS or CTS-to-self. */
+ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
+ /* NB: Group frames are sent using CCK in 802.11b/g. */
+ if (totlen + IEEE80211_CRC_LEN > ic->ic_rtsthreshold) {
+ flags |= IWN_TX_NEED_RTS;
+ } else if ((ic->ic_flags & IEEE80211_F_USEPROT) &&
+ ridx >= IWN_RIDX_OFDM6) {
+ if (ic->ic_protmode == IEEE80211_PROT_CTSONLY)
+ flags |= IWN_TX_NEED_CTS;
+ else if (ic->ic_protmode == IEEE80211_PROT_RTSCTS)
+ flags |= IWN_TX_NEED_RTS;
+ }
+ if (flags & (IWN_TX_NEED_RTS | IWN_TX_NEED_CTS)) {
+ if (sc->hw_type != IWN_HW_REV_TYPE_4965) {
+ /* 5000 autoselects RTS/CTS or CTS-to-self. */
+ flags &= ~(IWN_TX_NEED_RTS | IWN_TX_NEED_CTS);
+ flags |= IWN_TX_NEED_PROTECTION;
+ } else
+ flags |= IWN_TX_FULL_TXOP;
+ }
+ }
+
+ if (IEEE80211_IS_MULTICAST(wh->i_addr1) ||
+ type != IEEE80211_FC0_TYPE_DATA)
+ tx->id = sc->broadcast_id;
+ else
+ tx->id = wn->id;
+
+ if (type == IEEE80211_FC0_TYPE_MGT) {
+ uint8_t subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
+
+#ifndef IEEE80211_STA_ONLY
+ /* Tell HW to set timestamp in probe responses. */
+ /* XXX NetBSD rev 1.11 added probe requests here but */
+ /* probe requests do not take timestamps (from Bergamini). */
+ if (subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP)
+ flags |= IWN_TX_INSERT_TSTAMP;
+#endif
+ /* XXX NetBSD rev 1.11 and 1.20 added AUTH/DAUTH and RTS/CTS */
+ /* changes here. These are not needed (from Bergamini). */
+ if (subtype == IEEE80211_FC0_SUBTYPE_ASSOC_REQ ||
+ subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ)
+ tx->timeout = htole16(3);
+ else
+ tx->timeout = htole16(2);
+ } else
+ tx->timeout = htole16(0);
+
+ if (hdrlen & 3) {
+ /* First segment length must be a multiple of 4. */
+ flags |= IWN_TX_NEED_PADDING;
+ pad = 4 - (hdrlen & 3);
+ } else
+ pad = 0;
+
+ if (tid != WME_TID_INVALID) {
+ flags &= ~IWN_TX_AUTO_SEQ;
+ } else {
+ flags |= IWN_TX_AUTO_SEQ;
+ tid = 0;
+ }
+
+ tx->len = htole16(totlen);
+ tx->tid = tid;
+ tx->rts_ntries = 60;
+ tx->data_ntries = 15;
+ tx->lifetime = htole32(IWN_LIFETIME_INFINITE);
+ tx->plcp = rinfo->plcp;
+ tx->rflags = rinfo->flags;
+ if (tx->id == sc->broadcast_id) {
+ /* Group or management frame. */
+ tx->linkq = 0;
+ /* XXX Alternate between antenna A and B? */
+ txant = IWN_LSB(sc->txchainmask);
+ tx->rflags |= IWN_RFLAG_ANT(txant);
+ } else {
+ tx->linkq = in->in_rates.ir_nrates - in->in_txrate - 1;
+ flags |= IWN_TX_LINKQ; /* enable MRR */
+ }
+ /* Set physical address of "scratch area". */
+ tx->loaddr = htole32(IWN_LOADDR(data->scratch_paddr));
+ tx->hiaddr = IWN_HIADDR(data->scratch_paddr);
+
+ /* Copy 802.11 header in TX command. */
+ /* XXX NetBSD changed this in rev 1.20 */
+ memcpy(((uint8_t *)tx) + sizeof(*tx), wh, hdrlen);
+ mp->b_rptr += hdrlen;
+
+ bcopy(mp->b_rptr, data->dma_data.vaddr, totlen - hdrlen);
+ tx->security = 0;
+ tx->flags = htole32(flags);
+
+ data->ni = in;
+
+ DTRACE_PROBE4(tx, int, ring->qid, int, ring->cur, size_t, MBLKL(mp),
+ int, data->dma_data.ncookies);
+
+ /* Fill TX descriptor. */
+ desc->nsegs = 1 + data->dma_data.ncookies;
+ /* First DMA segment is used by the TX command. */
+ desc->segs[0].addr = htole32(IWN_LOADDR(data->cmd_paddr));
+ desc->segs[0].len = htole16(IWN_HIADDR(data->cmd_paddr) |
+ (4 + sizeof (*tx) + hdrlen + pad) << 4);
+
+ /* Other DMA segments are for data payload. */
+ cookie = data->dma_data.cookie;
+ for (i = 1, seglen = totlen - hdrlen;
+ i <= data->dma_data.ncookies;
+ i++, seglen -= cookie.dmac_size) {
+ desc->segs[i].addr = htole32(IWN_LOADDR(cookie.dmac_laddress));
+ desc->segs[i].len = htole16(IWN_HIADDR(cookie.dmac_laddress) |
+ seglen << 4);
+ if (i < data->dma_data.ncookies)
+ ddi_dma_nextcookie(data->dma_data.dma_hdl, &cookie);
+ }
+
+ (void) ddi_dma_sync(data->dma_data.dma_hdl, 0, 0, DDI_DMA_SYNC_FORDEV);
+ (void) ddi_dma_sync(ring->cmd_dma.dma_hdl, ring->cur * sizeof (*cmd),
+ sizeof (*cmd), DDI_DMA_SYNC_FORDEV);
+ (void) ddi_dma_sync(ring->desc_dma.dma_hdl, ring->cur * sizeof (*desc),
+ sizeof (*desc), DDI_DMA_SYNC_FORDEV);
+
+ /* Update TX scheduler. */
+ sc->ops.update_sched(sc, ring->qid, ring->cur, tx->id, totlen);
+
+ /* Kick TX ring. */
+ ring->cur = (ring->cur + 1) % IWN_TX_RING_COUNT;
+ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, ring->qid << 8 | ring->cur);
+
+ /* Mark TX ring as full if we reach a certain threshold. */
+ if (++ring->queued > IWN_TX_RING_HIMARK)
+ sc->qfullmsk |= 1 << ring->qid;
+ mutex_exit(&sc->sc_tx_mtx);
+ freemsg(mp);
+
+ ic->ic_stats.is_tx_bytes += totlen;
+
+ mutex_enter(&sc->sc_mt_mtx);
+ if (sc->sc_tx_timer == 0)
+ sc->sc_tx_timer = 5;
+ mutex_exit(&sc->sc_mt_mtx);
+
+ return 0;
+}
+
+static mblk_t *
+iwn_m_tx(void *arg, mblk_t *mp)
+{
+ struct iwn_softc *sc;
+ ieee80211com_t *ic;
+ mblk_t *next;
+
+ sc = (struct iwn_softc *)arg;
+ ASSERT(sc != NULL);
+ ic = &sc->sc_ic;
+
+ if (sc->sc_flags & IWN_FLAG_SUSPEND) {
+ freemsgchain(mp);
+ return (NULL);
+ }
+
+ if (ic->ic_state != IEEE80211_S_RUN) {
+ freemsgchain(mp);
+ return (NULL);
+ }
+
+ if ((sc->sc_flags & IWN_FLAG_HW_ERR_RECOVER)) {
+ freemsgchain(mp);
+ return (NULL);
+ }
+
+ while (mp != NULL) {
+ next = mp->b_next;
+ mp->b_next = NULL;
+ if (iwn_send(ic, mp, IEEE80211_FC0_TYPE_DATA) == EAGAIN) {
+ mp->b_next = next;
+ break;
+ }
+ mp = next;
+ }
+
+ return (mp);
+}
+
+static void
+iwn_watchdog(void *arg)
+{
+ struct iwn_softc *sc = (struct iwn_softc *)arg;
+ ieee80211com_t *ic = &sc->sc_ic;
+ timeout_id_t timeout_id = ic->ic_watchdog_timer;
+
+ ieee80211_stop_watchdog(ic);
+
+ mutex_enter(&sc->sc_mt_mtx);
+ if (sc->sc_tx_timer > 0) {
+ if (--sc->sc_tx_timer == 0) {
+ dev_err(sc->sc_dip, CE_WARN, "!device timeout");
+ sc->sc_flags |= IWN_FLAG_HW_ERR_RECOVER;
+ sc->sc_ostate = IEEE80211_S_RUN;
+ DTRACE_PROBE(recover__send__fail);
+ }
+ }
+ mutex_exit(&sc->sc_mt_mtx);
+
+ if ((ic->ic_state != IEEE80211_S_AUTH) &&
+ (ic->ic_state != IEEE80211_S_ASSOC))
+ return;
+
+ if (ic->ic_bss->in_fails > 10) {
+ DTRACE_PROBE2(watchdog__reset, timeout_id_t, timeout_id,
+ struct ieee80211node *, ic->ic_bss);
+ dev_err(sc->sc_dip, CE_WARN, "!iwn_watchdog reset");
+ ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
+ } else {
+ ic->ic_bss->in_fails++;
+
+ DTRACE_PROBE2(watchdog__timeout, timeout_id_t, timeout_id,
+ struct ieee80211node *, ic->ic_bss);
+
+ ieee80211_watchdog(ic);
+ }
+}
+
+static void
+iwn_m_ioctl(void *arg, queue_t *wq, mblk_t *mp)
+{
+ struct iwn_softc *sc;
+ struct ieee80211com *ic;
+ int error = 0;
+
+ sc = (struct iwn_softc *)arg;
+ ASSERT(sc != NULL);
+ ic = &sc->sc_ic;
+
+ mutex_enter(&sc->sc_mtx);
+ while (sc->sc_flags & IWN_FLAG_SCANNING)
+ cv_wait(&sc->sc_scan_cv, &sc->sc_mtx);
+ mutex_exit(&sc->sc_mtx);
+
+ error = ieee80211_ioctl(ic, wq, mp);
+ if (error == ENETRESET) {
+ /*
+ * This is special for the hidden AP connection.
+ * In any case, we should make sure only one 'scan'
+ * in the driver for a 'connect' CLI command. So
+ * when connecting to a hidden AP, the scan is just
+ * sent out to the air when we know the desired
+ * essid of the AP we want to connect.
+ */
+ if (ic->ic_des_esslen) {
+ if (sc->sc_flags & IWN_FLAG_RUNNING) {
+ DTRACE_PROBE(netreset);
+ iwn_m_stop(sc);
+ (void) iwn_m_start(sc);
+ (void) ieee80211_new_state(ic,
+ IEEE80211_S_SCAN, -1);
+ }
+ }
+ }
+}
+
+/*
+ * Call back functions for get/set property
+ */
+static int
+iwn_m_getprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num,
+ uint_t wldp_length, void *wldp_buf)
+{
+ struct iwn_softc *sc;
+
+ sc = (struct iwn_softc *)arg;
+ ASSERT(sc != NULL);
+
+ return (ieee80211_getprop(&sc->sc_ic, pr_name, wldp_pr_num,
+ wldp_length, wldp_buf));
+}
+
+static void
+iwn_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num,
+ mac_prop_info_handle_t prh)
+{
+ struct iwn_softc *sc;
+
+ sc = (struct iwn_softc *)arg;
+ ASSERT(sc != NULL);
+
+ ieee80211_propinfo(&sc->sc_ic, pr_name, wldp_pr_num, prh);
+}
+
+static int
+iwn_m_setprop(void *arg, const char *pr_name, mac_prop_id_t wldp_pr_num,
+ uint_t wldp_length, const void *wldp_buf)
+{
+ struct iwn_softc *sc;
+ ieee80211com_t *ic;
+ int err = EINVAL;
+
+ sc = (struct iwn_softc *)arg;
+ ASSERT(sc != NULL);
+ ic = &sc->sc_ic;
+
+ mutex_enter(&sc->sc_mtx);
+ while (sc->sc_flags & IWN_FLAG_SCANNING)
+ cv_wait(&sc->sc_scan_cv, &sc->sc_mtx);
+ mutex_exit(&sc->sc_mtx);
+
+ err = ieee80211_setprop(ic, pr_name, wldp_pr_num, wldp_length,
+ wldp_buf);
+
+ if (err == ENETRESET) {
+ if (ic->ic_des_esslen) {
+ if (sc->sc_flags & IWN_FLAG_RUNNING) {
+ DTRACE_PROBE(netreset);
+ iwn_m_stop(sc);
+ (void) iwn_m_start(sc);
+ (void) ieee80211_new_state(ic,
+ IEEE80211_S_SCAN, -1);
+ }
+ }
+ err = 0;
+ }
+
+ return (err);
+}
+
+/*
+ * invoked by GLD get statistics from NIC and driver
+ */
+static int
+iwn_m_stat(void *arg, uint_t stat, uint64_t *val)
+{
+ struct iwn_softc *sc;
+ ieee80211com_t *ic;
+ ieee80211_node_t *in;
+
+ sc = (struct iwn_softc *)arg;
+ ASSERT(sc != NULL);
+ ic = &sc->sc_ic;
+
+ mutex_enter(&sc->sc_mtx);
+
+ switch (stat) {
+ case MAC_STAT_IFSPEED:
+ in = ic->ic_bss;
+ *val = ((IEEE80211_FIXED_RATE_NONE == ic->ic_fixed_rate) ?
+ IEEE80211_RATE(in->in_txrate) :
+ ic->ic_fixed_rate) / 2 * 1000000;
+ break;
+ case MAC_STAT_NOXMTBUF:
+ *val = sc->sc_tx_nobuf;
+ break;
+ case MAC_STAT_NORCVBUF:
+ *val = sc->sc_rx_nobuf;
+ break;
+ case MAC_STAT_IERRORS:
+ *val = sc->sc_rx_err;
+ break;
+ case MAC_STAT_RBYTES:
+ *val = ic->ic_stats.is_rx_bytes;
+ break;
+ case MAC_STAT_IPACKETS:
+ *val = ic->ic_stats.is_rx_frags;
+ break;
+ case MAC_STAT_OBYTES:
+ *val = ic->ic_stats.is_tx_bytes;
+ break;
+ case MAC_STAT_OPACKETS:
+ *val = ic->ic_stats.is_tx_frags;
+ break;
+ case MAC_STAT_OERRORS:
+ case WIFI_STAT_TX_FAILED:
+ *val = sc->sc_tx_err;
+ break;
+ case WIFI_STAT_TX_RETRANS:
+ *val = sc->sc_tx_retries;
+ break;
+ case WIFI_STAT_FCS_ERRORS:
+ case WIFI_STAT_WEP_ERRORS:
+ case WIFI_STAT_TX_FRAGS:
+ case WIFI_STAT_MCAST_TX:
+ case WIFI_STAT_RTS_SUCCESS:
+ case WIFI_STAT_RTS_FAILURE:
+ case WIFI_STAT_ACK_FAILURE:
+ case WIFI_STAT_RX_FRAGS:
+ case WIFI_STAT_MCAST_RX:
+ case WIFI_STAT_RX_DUPS:
+ mutex_exit(&sc->sc_mtx);
+ return (ieee80211_stat(ic, stat, val));
+ default:
+ mutex_exit(&sc->sc_mtx);
+ return (ENOTSUP);
+ }
+
+ mutex_exit(&sc->sc_mtx);
+
+ return (0);
+
+}
+
+/*
+ * invoked by GLD to configure NIC
+ */
+static int
+iwn_m_unicst(void *arg, const uint8_t *macaddr)
+{
+ struct iwn_softc *sc;
+ ieee80211com_t *ic;
+ int err = IWN_SUCCESS;
+
+ sc = (struct iwn_softc *)arg;
+ ASSERT(sc != NULL);
+ ic = &sc->sc_ic;
+
+ if (!IEEE80211_ADDR_EQ(ic->ic_macaddr, macaddr)) {
+ mutex_enter(&sc->sc_mtx);
+ IEEE80211_ADDR_COPY(ic->ic_macaddr, macaddr);
+ err = iwn_config(sc);
+ mutex_exit(&sc->sc_mtx);
+ if (err != IWN_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN, "!iwn_m_unicst(): "
+ "failed to configure device");
+ goto fail;
+ }
+ }
+
+ return (err);
+
+fail:
+ return (err);
+}
+
+/*ARGSUSED*/
+static int
+iwn_m_multicst(void *arg, boolean_t add, const uint8_t *m)
+{
+ return (IWN_SUCCESS);
+}
+
+/*ARGSUSED*/
+static int
+iwn_m_promisc(void *arg, boolean_t on)
+{
+ _NOTE(ARGUNUSED(on));
+
+ return (IWN_SUCCESS);
+}
+
+static void
+iwn_abort_scan(void *arg)
+{
+ struct iwn_softc *sc = (struct iwn_softc *)arg;
+ ieee80211com_t *ic = &sc->sc_ic;
+
+ mutex_enter(&sc->sc_mtx);
+ if ((sc->sc_flags & IWN_FLAG_SCANNING) == 0)
+ return;
+
+ dev_err(sc->sc_dip, CE_WARN,
+ "!aborting scan, flags = %x, state = %s",
+ sc->sc_flags, ieee80211_state_name[ic->ic_state]);
+ sc->sc_flags &= ~IWN_FLAG_SCANNING;
+ iwn_hw_stop(sc, B_FALSE);
+ mutex_exit(&sc->sc_mtx);
+
+ sc->scan_to = 0;
+ (void) iwn_init(sc);
+ ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
+}
+
+/*
+ * periodic function to deal with RF switch and HW error recovery
+ */
+static void
+iwn_periodic(void *arg)
+{
+ struct iwn_softc *sc = (struct iwn_softc *)arg;
+ ieee80211com_t *ic = &sc->sc_ic;
+ int err;
+ uint32_t tmp;
+
+ mutex_enter(&sc->sc_mtx);
+ tmp = IWN_READ(sc, IWN_GP_CNTRL);
+ if (tmp & IWN_GP_CNTRL_RFKILL) {
+ sc->sc_flags &= ~IWN_FLAG_RADIO_OFF;
+ } else {
+ sc->sc_flags |= IWN_FLAG_RADIO_OFF;
+ }
+
+ /*
+ * If the RF is OFF, do nothing.
+ */
+ if (sc->sc_flags & IWN_FLAG_RADIO_OFF) {
+ mutex_exit(&sc->sc_mtx);
+ return;
+ }
+
+ mutex_exit(&sc->sc_mtx);
+
+ /*
+ * recovery fatal error
+ */
+ if (ic->ic_mach &&
+ (sc->sc_flags & IWN_FLAG_HW_ERR_RECOVER)) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!trying to restore previous state");
+
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags |= IWN_FLAG_STOP_CALIB_TO;
+ mutex_exit(&sc->sc_mtx);
+
+ if (sc->calib_to != 0)
+ (void) untimeout(sc->calib_to);
+ sc->calib_to = 0;
+
+ if (sc->scan_to != 0)
+ (void) untimeout(sc->scan_to);
+ sc->scan_to = 0;
+
+ iwn_hw_stop(sc, B_TRUE);
+
+ if (IWN_CHK_FAST_RECOVER(sc)) {
+ /* save runtime configuration */
+ bcopy(&sc->rxon, &sc->rxon_save, sizeof (sc->rxon));
+ } else {
+ ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
+ }
+
+ err = iwn_init(sc);
+ if (err != IWN_SUCCESS)
+ return;
+
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags |= IWN_FLAG_RUNNING;
+ mutex_exit(&sc->sc_mtx);
+
+ if (!IWN_CHK_FAST_RECOVER(sc) ||
+ iwn_fast_recover(sc) != IWN_SUCCESS) {
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags &= ~IWN_FLAG_HW_ERR_RECOVER;
+ mutex_exit(&sc->sc_mtx);
+ if (sc->sc_ostate != IEEE80211_S_INIT) {
+ ieee80211_new_state(ic, IEEE80211_S_SCAN, 0);
+ }
+ }
+ }
+}
+
+/*
+ * Send a command to the firmware.
+ */
+static int
+iwn_cmd(struct iwn_softc *sc, uint8_t code, void *buf, int size, int async)
+{
+ struct iwn_tx_ring *ring = &sc->txq[IWN_CMD_QUEUE_NUM];
+ struct iwn_tx_desc *desc;
+ struct iwn_tx_data *data;
+ struct iwn_tx_cmd *cmd;
+ clock_t clk;
+ uintptr_t paddr;
+ int totlen, ret;
+
+ ASSERT(mutex_owned(&sc->sc_mtx));
+
+ desc = &ring->desc[ring->cur];
+ data = &ring->data[ring->cur];
+ totlen = 4 + size;
+
+ if (size > sizeof (cmd->data)) {
+ /* Command is too large to fit in a descriptor. */
+ if (iwn_dma_contig_alloc(sc, &data->cmd_dma, totlen,
+ DDI_DMA_CONSISTENT | DDI_DMA_RDWR, (void **)&cmd,
+ &iwn_dma_accattr, 1) != DDI_SUCCESS)
+ return ENOBUFS;
+ paddr = data->cmd_dma.paddr;
+ } else {
+ cmd = &ring->cmd[ring->cur];
+ paddr = data->cmd_paddr;
+ }
+
+ cmd->code = code;
+ cmd->flags = 0;
+ cmd->qid = ring->qid;
+ cmd->idx = ring->cur;
+ bzero(cmd->data, size);
+ memcpy(cmd->data, buf, size);
+
+ bzero(desc, sizeof(*desc));
+ desc->nsegs = 1;
+ desc->segs[0].addr = htole32(IWN_LOADDR(paddr));
+ desc->segs[0].len = htole16(IWN_HIADDR(paddr) | totlen << 4);
+
+ if (size > sizeof cmd->data) {
+ (void) ddi_dma_sync(data->cmd_dma.dma_hdl, 0, totlen,
+ DDI_DMA_SYNC_FORDEV);
+ } else {
+ (void) ddi_dma_sync(ring->cmd_dma.dma_hdl,
+ ring->cur * sizeof (*cmd),
+ totlen, DDI_DMA_SYNC_FORDEV);
+ }
+ (void) ddi_dma_sync(ring->desc_dma.dma_hdl,
+ ring->cur * sizeof (*desc),
+ sizeof (*desc), DDI_DMA_SYNC_FORDEV);
+
+ /* Update TX scheduler. */
+ sc->ops.update_sched(sc, ring->qid, ring->cur, 0, 0);
+
+ /* Kick command ring. */
+ ring->cur = (ring->cur + 1) % IWN_TX_RING_COUNT;
+ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, ring->qid << 8 | ring->cur);
+
+ if (async)
+ return (IWN_SUCCESS);
+
+ sc->sc_cmd_flag = SC_CMD_FLG_NONE;
+ clk = ddi_get_lbolt() + drv_usectohz(2000000);
+ while (sc->sc_cmd_flag != SC_CMD_FLG_DONE)
+ if (cv_timedwait(&sc->sc_cmd_cv, &sc->sc_mtx, clk) < 0)
+ break;
+
+ ret = (sc->sc_cmd_flag == SC_CMD_FLG_DONE) ? IWN_SUCCESS : IWN_FAIL;
+ sc->sc_cmd_flag = SC_CMD_FLG_NONE;
+
+ return (ret);
+}
+
+static int
+iwn4965_add_node(struct iwn_softc *sc, struct iwn_node_info *node, int async)
+{
+ struct iwn4965_node_info hnode;
+ char *src, *dst;
+
+ /*
+ * We use the node structure for 5000 Series internally (it is
+ * a superset of the one for 4965AGN). We thus copy the common
+ * fields before sending the command.
+ */
+ src = (char *)node;
+ dst = (char *)&hnode;
+ memcpy(dst, src, 48);
+ /* Skip TSC, RX MIC and TX MIC fields from ``src''. */
+ memcpy(dst + 48, src + 72, 20);
+ return iwn_cmd(sc, IWN_CMD_ADD_NODE, &hnode, sizeof hnode, async);
+}
+
+static int
+iwn5000_add_node(struct iwn_softc *sc, struct iwn_node_info *node, int async)
+{
+ /* Direct mapping. */
+ return iwn_cmd(sc, IWN_CMD_ADD_NODE, node, sizeof (*node), async);
+}
+
+static int
+iwn_set_link_quality(struct iwn_softc *sc, struct ieee80211_node *ni)
+{
+ struct iwn_node *wn = (void *)ni;
+ struct ieee80211_rateset *rs = &ni->in_rates;
+ struct iwn_cmd_link_quality linkq;
+ const struct iwn_rate *rinfo;
+ uint8_t txant;
+ int i, txrate;
+
+ /* Use the first valid TX antenna. */
+ txant = IWN_LSB(sc->txchainmask);
+
+ memset(&linkq, 0, sizeof linkq);
+ linkq.id = wn->id;
+ linkq.antmsk_1stream = txant;
+ linkq.antmsk_2stream = IWN_ANT_AB;
+ linkq.ampdu_max = 31;
+ linkq.ampdu_threshold = 3;
+ linkq.ampdu_limit = htole16(4000); /* 4ms */
+
+ /* Start at highest available bit-rate. */
+ txrate = rs->ir_nrates - 1;
+ for (i = 0; i < IWN_MAX_TX_RETRIES; i++) {
+ rinfo = &iwn_rates[wn->ridx[txrate]];
+ linkq.retry[i].plcp = rinfo->plcp;
+ linkq.retry[i].rflags = rinfo->flags;
+ linkq.retry[i].rflags |= IWN_RFLAG_ANT(txant);
+ /* Next retry at immediate lower bit-rate. */
+ if (txrate > 0)
+ txrate--;
+ }
+ return iwn_cmd(sc, IWN_CMD_LINK_QUALITY, &linkq, sizeof linkq, 1);
+}
+
+/*
+ * Broadcast node is used to send group-addressed and management frames.
+ */
+static int
+iwn_add_broadcast_node(struct iwn_softc *sc, int async)
+{
+ struct iwn_ops *ops = &sc->ops;
+ struct iwn_node_info node;
+ struct iwn_cmd_link_quality linkq;
+ const struct iwn_rate *rinfo;
+ uint8_t txant;
+ int i, error;
+
+ memset(&node, 0, sizeof node);
+ IEEE80211_ADDR_COPY(node.macaddr, etherbroadcastaddr);
+ node.id = sc->broadcast_id;
+ DTRACE_PROBE(add__broadcast__node);
+ if ((error = ops->add_node(sc, &node, async)) != 0)
+ return error;
+
+ /* Use the first valid TX antenna. */
+ txant = IWN_LSB(sc->txchainmask);
+
+ memset(&linkq, 0, sizeof linkq);
+ linkq.id = sc->broadcast_id;
+ linkq.antmsk_1stream = txant;
+ linkq.antmsk_2stream = IWN_ANT_AB;
+ linkq.ampdu_max = 64;
+ linkq.ampdu_threshold = 3;
+ linkq.ampdu_limit = htole16(4000); /* 4ms */
+
+ /* Use lowest mandatory bit-rate. */
+ rinfo = (sc->sc_ic.ic_curmode != IEEE80211_MODE_11A) ?
+ &iwn_rates[IWN_RIDX_CCK1] : &iwn_rates[IWN_RIDX_OFDM6];
+ linkq.retry[0].plcp = rinfo->plcp;
+ linkq.retry[0].rflags = rinfo->flags;
+ linkq.retry[0].rflags |= IWN_RFLAG_ANT(txant);
+ /* Use same bit-rate for all TX retries. */
+ for (i = 1; i < IWN_MAX_TX_RETRIES; i++) {
+ linkq.retry[i].plcp = linkq.retry[0].plcp;
+ linkq.retry[i].rflags = linkq.retry[0].rflags;
+ }
+ return iwn_cmd(sc, IWN_CMD_LINK_QUALITY, &linkq, sizeof linkq, async);
+}
+
+static void
+iwn_set_led(struct iwn_softc *sc, uint8_t which, uint8_t off, uint8_t on)
+{
+ struct iwn_cmd_led led;
+
+ /* Clear microcode LED ownership. */
+ IWN_CLRBITS(sc, IWN_LED, IWN_LED_BSM_CTRL);
+
+ led.which = which;
+ led.unit = htole32(10000); /* on/off in unit of 100ms */
+ led.off = off;
+ led.on = on;
+ DTRACE_PROBE1(led__change, const char *,
+ (off != 0 && on != 0) ? "blinking" :
+ (off != 0) ? "off" : "on");
+ (void)iwn_cmd(sc, IWN_CMD_SET_LED, &led, sizeof led, 1);
+}
+
+/*
+ * Set the critical temperature at which the firmware will stop the radio
+ * and notify us.
+ */
+static int
+iwn_set_critical_temp(struct iwn_softc *sc)
+{
+ struct iwn_critical_temp crit;
+ int32_t temp;
+
+ IWN_WRITE(sc, IWN_UCODE_GP1_CLR, IWN_UCODE_GP1_CTEMP_STOP_RF);
+
+ if (sc->hw_type == IWN_HW_REV_TYPE_5150)
+ temp = (IWN_CTOK(110) - sc->temp_off) * -5;
+ else if (sc->hw_type == IWN_HW_REV_TYPE_4965)
+ temp = IWN_CTOK(110);
+ else
+ temp = 110;
+
+ sc->sc_misc->crit_temp.value.ul = temp;
+
+ memset(&crit, 0, sizeof crit);
+ crit.tempR = htole32(temp);
+ return iwn_cmd(sc, IWN_CMD_SET_CRITICAL_TEMP, &crit, sizeof crit, 0);
+}
+
+static int
+iwn_set_timing(struct iwn_softc *sc, struct ieee80211_node *ni)
+{
+ struct iwn_cmd_timing cmd;
+ uint64_t val, mod;
+
+ memset(&cmd, 0, sizeof cmd);
+ memcpy(&cmd.tstamp, ni->in_tstamp.data, sizeof (uint64_t));
+ cmd.bintval = htole16(ni->in_intval);
+ cmd.lintval = htole16(10);
+
+ /* Compute remaining time until next beacon. */
+ val = (uint64_t)ni->in_intval * 1024; /* msecs -> usecs */
+ mod = le64toh(cmd.tstamp) % val;
+ cmd.binitval = htole32((uint32_t)(val - mod));
+
+ sc->sc_timing->bintval.value.ul = ni->in_intval;
+ sc->sc_timing->tstamp.value.ul = ni->in_tstamp.tsf;
+ sc->sc_timing->init.value.ul = (uint32_t)(val - mod);
+
+ return iwn_cmd(sc, IWN_CMD_TIMING, &cmd, sizeof cmd, 1);
+}
+
+static void
+iwn4965_power_calibration(struct iwn_softc *sc, int temp)
+{
+ /* Adjust TX power if need be (delta >= 3 degC). */
+ IWN_DBG("temperature %d->%d", sc->temp, temp);
+ if (abs(temp - sc->temp) >= 3) {
+ /* Record temperature of last calibration. */
+ sc->temp = temp;
+ (void)iwn4965_set_txpower(sc, 1);
+ }
+}
+
+/*
+ * Set TX power for current channel (each rate has its own power settings).
+ * This function takes into account the regulatory information from EEPROM,
+ * the current temperature and the current voltage.
+ */
+static int
+iwn4965_set_txpower(struct iwn_softc *sc, int async)
+{
+/* Fixed-point arithmetic division using a n-bit fractional part. */
+#define fdivround(a, b, n) \
+ ((((1 << n) * (a)) / (b) + (1 << n) / 2) / (1 << n))
+/* Linear interpolation. */
+#define interpolate(x, x1, y1, x2, y2, n) \
+ ((y1) + fdivround(((int)(x) - (x1)) * ((y2) - (y1)), (x2) - (x1), n))
+
+ static const int tdiv[IWN_NATTEN_GROUPS] = { 9, 8, 8, 8, 6 };
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct iwn_ucode_info *uc = &sc->ucode_info;
+ struct ieee80211_channel *ch;
+ struct iwn4965_cmd_txpower cmd;
+ struct iwn4965_eeprom_chan_samples *chans;
+ const uint8_t *rf_gain, *dsp_gain;
+ int32_t vdiff, tdiff;
+ int i, c, grp, maxpwr;
+ uint8_t chan;
+
+ /* Retrieve current channel from last RXON. */
+ chan = sc->rxon.chan;
+ sc->sc_txpower->chan.value.l = chan;
+ ch = &ic->ic_sup_channels[chan];
+
+ memset(&cmd, 0, sizeof cmd);
+ cmd.band = IEEE80211_IS_CHAN_5GHZ(ch) ? 0 : 1;
+ cmd.chan = chan;
+
+ if (IEEE80211_IS_CHAN_5GHZ(ch)) {
+ maxpwr = sc->maxpwr5GHz;
+ rf_gain = iwn4965_rf_gain_5ghz;
+ dsp_gain = iwn4965_dsp_gain_5ghz;
+ } else {
+ maxpwr = sc->maxpwr2GHz;
+ rf_gain = iwn4965_rf_gain_2ghz;
+ dsp_gain = iwn4965_dsp_gain_2ghz;
+ }
+
+ /* Compute voltage compensation. */
+ vdiff = ((int32_t)le32toh(uc->volt) - sc->eeprom_voltage) / 7;
+ if (vdiff > 0)
+ vdiff *= 2;
+ if (abs(vdiff) > 2)
+ vdiff = 0;
+ sc->sc_txpower->vdiff.value.l = vdiff;
+
+ /* Get channel attenuation group. */
+ if (chan <= 20) /* 1-20 */
+ grp = 4;
+ else if (chan <= 43) /* 34-43 */
+ grp = 0;
+ else if (chan <= 70) /* 44-70 */
+ grp = 1;
+ else if (chan <= 124) /* 71-124 */
+ grp = 2;
+ else /* 125-200 */
+ grp = 3;
+ sc->sc_txpower->group.value.l = grp;
+
+ /* Get channel sub-band. */
+ for (i = 0; i < IWN_NBANDS; i++)
+ if (sc->bands[i].lo != 0 &&
+ sc->bands[i].lo <= chan && chan <= sc->bands[i].hi)
+ break;
+ if (i == IWN_NBANDS) /* Can't happen in real-life. */
+ return EINVAL;
+ chans = sc->bands[i].chans;
+ sc->sc_txpower->subband.value.l = i;
+
+ for (c = 0; c < 2; c++) {
+ uint8_t power, gain, temp;
+ int maxchpwr, pwr, ridx, idx;
+
+ power = interpolate(chan,
+ chans[0].num, chans[0].samples[c][1].power,
+ chans[1].num, chans[1].samples[c][1].power, 1);
+ gain = interpolate(chan,
+ chans[0].num, chans[0].samples[c][1].gain,
+ chans[1].num, chans[1].samples[c][1].gain, 1);
+ temp = interpolate(chan,
+ chans[0].num, chans[0].samples[c][1].temp,
+ chans[1].num, chans[1].samples[c][1].temp, 1);
+ sc->sc_txpower->txchain[c].power.value.l = power;
+ sc->sc_txpower->txchain[c].gain.value.l = gain;
+ sc->sc_txpower->txchain[c].temp.value.l = temp;
+
+ /* Compute temperature compensation. */
+ tdiff = ((sc->temp - temp) * 2) / tdiv[grp];
+ sc->sc_txpower->txchain[c].tcomp.value.l = tdiff;
+
+ for (ridx = 0; ridx <= IWN_RIDX_MAX; ridx++) {
+ /* Convert dBm to half-dBm. */
+ maxchpwr = sc->maxpwr[chan] * 2;
+ if ((ridx / 8) & 1)
+ maxchpwr -= 6; /* MIMO 2T: -3dB */
+
+ pwr = maxpwr;
+
+ /* Adjust TX power based on rate. */
+ if ((ridx % 8) == 5)
+ pwr -= 15; /* OFDM48: -7.5dB */
+ else if ((ridx % 8) == 6)
+ pwr -= 17; /* OFDM54: -8.5dB */
+ else if ((ridx % 8) == 7)
+ pwr -= 20; /* OFDM60: -10dB */
+ else
+ pwr -= 10; /* Others: -5dB */
+
+ /* Do not exceed channel max TX power. */
+ if (pwr > maxchpwr)
+ pwr = maxchpwr;
+
+ idx = gain - (pwr - power) - tdiff - vdiff;
+ if ((ridx / 8) & 1) /* MIMO */
+ idx += (int32_t)le32toh(uc->atten[grp][c]);
+
+ if (cmd.band == 0)
+ idx += 9; /* 5GHz */
+ if (ridx == IWN_RIDX_MAX)
+ idx += 5; /* CCK */
+
+ /* Make sure idx stays in a valid range. */
+ if (idx < 0)
+ idx = 0;
+ else if (idx > IWN4965_MAX_PWR_INDEX)
+ idx = IWN4965_MAX_PWR_INDEX;
+
+ sc->sc_txpower->txchain[c].rate[ridx].rf_gain.value.l =
+ cmd.power[ridx].rf_gain[c] = rf_gain[idx];
+ sc->sc_txpower->txchain[c].rate[ridx].dsp_gain.value.l =
+ cmd.power[ridx].dsp_gain[c] = dsp_gain[idx];
+ }
+ }
+
+ return iwn_cmd(sc, IWN_CMD_TXPOWER, &cmd, sizeof cmd, async);
+
+#undef interpolate
+#undef fdivround
+}
+
+static int
+iwn5000_set_txpower(struct iwn_softc *sc, int async)
+{
+ struct iwn5000_cmd_txpower cmd;
+
+ /*
+ * TX power calibration is handled automatically by the firmware
+ * for 5000 Series.
+ */
+ memset(&cmd, 0, sizeof cmd);
+ cmd.global_limit = 2 * IWN5000_TXPOWER_MAX_DBM; /* 16 dBm */
+ cmd.flags = IWN5000_TXPOWER_NO_CLOSED;
+ cmd.srv_limit = IWN5000_TXPOWER_AUTO;
+ return iwn_cmd(sc, IWN_CMD_TXPOWER_DBM, &cmd, sizeof cmd, async);
+}
+
+/*
+ * Retrieve the maximum RSSI (in dBm) among receivers.
+ */
+static int
+iwn4965_get_rssi(const struct iwn_rx_stat *stat)
+{
+ const struct iwn4965_rx_phystat *phy = (const void *)stat->phybuf;
+ uint8_t mask, agc;
+ int rssi;
+
+ mask = (le16toh(phy->antenna) >> 4) & IWN_ANT_ABC;
+ agc = (le16toh(phy->agc) >> 7) & 0x7f;
+
+ rssi = 0;
+ if (mask & IWN_ANT_A)
+ rssi = MAX(rssi, phy->rssi[0]);
+ if (mask & IWN_ANT_B)
+ rssi = MAX(rssi, phy->rssi[2]);
+ if (mask & IWN_ANT_C)
+ rssi = MAX(rssi, phy->rssi[4]);
+
+ return rssi - agc - IWN_RSSI_TO_DBM;
+}
+
+static int
+iwn5000_get_rssi(const struct iwn_rx_stat *stat)
+{
+ const struct iwn5000_rx_phystat *phy = (const void *)stat->phybuf;
+ uint8_t agc;
+ int rssi;
+
+ agc = (le32toh(phy->agc) >> 9) & 0x7f;
+
+ rssi = MAX(le16toh(phy->rssi[0]) & 0xff,
+ le16toh(phy->rssi[1]) & 0xff);
+ rssi = MAX(le16toh(phy->rssi[2]) & 0xff, rssi);
+
+ return rssi - agc - IWN_RSSI_TO_DBM;
+}
+
+/*
+ * Retrieve the average noise (in dBm) among receivers.
+ */
+static int
+iwn_get_noise(const struct iwn_rx_general_stats *stats)
+{
+ int i, total, nbant, noise;
+
+ total = nbant = 0;
+ for (i = 0; i < 3; i++) {
+ if ((noise = le32toh(stats->noise[i]) & 0xff) == 0)
+ continue;
+ total += noise;
+ nbant++;
+ }
+ /* There should be at least one antenna but check anyway. */
+ return (nbant == 0) ? -127 : (total / nbant) - 107;
+}
+
+/*
+ * Compute temperature (in degC) from last received statistics.
+ */
+static int
+iwn4965_get_temperature(struct iwn_softc *sc)
+{
+ struct iwn_ucode_info *uc = &sc->ucode_info;
+ int32_t r1, r2, r3, r4, temp;
+
+ r1 = le32toh(uc->temp[0].chan20MHz);
+ r2 = le32toh(uc->temp[1].chan20MHz);
+ r3 = le32toh(uc->temp[2].chan20MHz);
+ r4 = le32toh(sc->rawtemp);
+
+ if (r1 == r3) /* Prevents division by 0 (should not happen). */
+ return 0;
+
+ /* Sign-extend 23-bit R4 value to 32-bit. */
+ r4 = ((r4 & 0xffffff) ^ 0x800000) - 0x800000;
+ /* Compute temperature in Kelvin. */
+ temp = (259 * (r4 - r2)) / (r3 - r1);
+ temp = (temp * 97) / 100 + 8;
+
+ return IWN_KTOC(temp);
+}
+
+static int
+iwn5000_get_temperature(struct iwn_softc *sc)
+{
+ int32_t temp;
+
+ /*
+ * Temperature is not used by the driver for 5000 Series because
+ * TX power calibration is handled by firmware. We export it to
+ * users through a kstat though.
+ */
+ temp = le32toh(sc->rawtemp);
+ if (sc->hw_type == IWN_HW_REV_TYPE_5150) {
+ temp = (temp / -5) + sc->temp_off;
+ temp = IWN_KTOC(temp);
+ }
+ return temp;
+}
+
+/*
+ * Initialize sensitivity calibration state machine.
+ */
+static int
+iwn_init_sensitivity(struct iwn_softc *sc)
+{
+ struct iwn_ops *ops = &sc->ops;
+ struct iwn_calib_state *calib = &sc->calib;
+ uint32_t flags;
+ int error;
+
+ /* Reset calibration state machine. */
+ memset(calib, 0, sizeof (*calib));
+ calib->state = IWN_CALIB_STATE_INIT;
+ calib->cck_state = IWN_CCK_STATE_HIFA;
+ /* Set initial correlation values. */
+ calib->ofdm_x1 = sc->limits->min_ofdm_x1;
+ calib->ofdm_mrc_x1 = sc->limits->min_ofdm_mrc_x1;
+ calib->ofdm_x4 = sc->limits->min_ofdm_x4;
+ calib->ofdm_mrc_x4 = sc->limits->min_ofdm_mrc_x4;
+ calib->cck_x4 = 125;
+ calib->cck_mrc_x4 = sc->limits->min_cck_mrc_x4;
+ calib->energy_cck = sc->limits->energy_cck;
+
+ /* Write initial sensitivity. */
+ if ((error = iwn_send_sensitivity(sc)) != 0)
+ return error;
+
+ /* Write initial gains. */
+ if ((error = ops->init_gains(sc)) != 0)
+ return error;
+
+ /* Request statistics at each beacon interval. */
+ flags = 0;
+ return iwn_cmd(sc, IWN_CMD_GET_STATISTICS, &flags, sizeof flags, 1);
+}
+
+/*
+ * Collect noise and RSSI statistics for the first 20 beacons received
+ * after association and use them to determine connected antennas and
+ * to set differential gains.
+ */
+static void
+iwn_collect_noise(struct iwn_softc *sc,
+ const struct iwn_rx_general_stats *stats)
+{
+ struct iwn_ops *ops = &sc->ops;
+ struct iwn_calib_state *calib = &sc->calib;
+ uint32_t val;
+ int i;
+
+ /* Accumulate RSSI and noise for all 3 antennas. */
+ for (i = 0; i < 3; i++) {
+ calib->rssi[i] += le32toh(stats->rssi[i]) & 0xff;
+ calib->noise[i] += le32toh(stats->noise[i]) & 0xff;
+ }
+ /* NB: We update differential gains only once after 20 beacons. */
+ if (++calib->nbeacons < 20)
+ return;
+
+ /* Determine highest average RSSI. */
+ val = MAX(calib->rssi[0], calib->rssi[1]);
+ val = MAX(calib->rssi[2], val);
+
+ /* Determine which antennas are connected. */
+ sc->chainmask = sc->rxchainmask;
+ for (i = 0; i < 3; i++)
+ if (val - calib->rssi[i] > 15 * 20)
+ sc->chainmask &= ~(1 << i);
+
+ sc->sc_ant->conn_ant.value.ul = sc->chainmask;
+
+ /* If none of the TX antennas are connected, keep at least one. */
+ if ((sc->chainmask & sc->txchainmask) == 0)
+ sc->chainmask |= IWN_LSB(sc->txchainmask);
+
+ (void)ops->set_gains(sc);
+ calib->state = IWN_CALIB_STATE_RUN;
+
+#ifdef notyet
+ /* XXX Disable RX chains with no antennas connected. */
+ sc->rxon.rxchain = htole16(IWN_RXCHAIN_SEL(sc->chainmask));
+ DTRACE_PROBE2(rxon, struct iwn_rxon *, &sc->rxon, int, sc->rxonsz);
+ (void)iwn_cmd(sc, IWN_CMD_RXON, &sc->rxon, sc->rxonsz, 1);
+#endif
+
+ /* Enable power-saving mode if requested by user. */
+ if (sc->sc_ic.ic_flags & IEEE80211_F_PMGTON)
+ (void)iwn_set_pslevel(sc, 0, 3, 1);
+}
+
+static int
+iwn4965_init_gains(struct iwn_softc *sc)
+{
+ struct iwn_phy_calib_gain cmd;
+
+ memset(&cmd, 0, sizeof cmd);
+ cmd.code = IWN4965_PHY_CALIB_DIFF_GAIN;
+ /* Differential gains initially set to 0 for all 3 antennas. */
+ return iwn_cmd(sc, IWN_CMD_PHY_CALIB, &cmd, sizeof cmd, 1);
+}
+
+static int
+iwn5000_init_gains(struct iwn_softc *sc)
+{
+ struct iwn_phy_calib cmd;
+
+ memset(&cmd, 0, sizeof cmd);
+ cmd.code = sc->reset_noise_gain;
+ cmd.ngroups = 1;
+ cmd.isvalid = 1;
+ return iwn_cmd(sc, IWN_CMD_PHY_CALIB, &cmd, sizeof cmd, 1);
+}
+
+static int
+iwn4965_set_gains(struct iwn_softc *sc)
+{
+ struct iwn_calib_state *calib = &sc->calib;
+ struct iwn_phy_calib_gain cmd;
+ int i, delta, noise;
+
+ /* Get minimal noise among connected antennas. */
+ noise = INT_MAX; /* NB: There's at least one antenna. */
+ for (i = 0; i < 3; i++)
+ if (sc->chainmask & (1 << i))
+ noise = MIN(calib->noise[i], noise);
+
+ memset(&cmd, 0, sizeof cmd);
+ cmd.code = IWN4965_PHY_CALIB_DIFF_GAIN;
+ /* Set differential gains for connected antennas. */
+ for (i = 0; i < 3; i++) {
+ if (sc->chainmask & (1 << i)) {
+ /* Compute attenuation (in unit of 1.5dB). */
+ delta = (noise - calib->noise[i]) / 30;
+ /* NB: delta <= 0 */
+ /* Limit to [-4.5dB,0]. */
+ cmd.gain[i] = (uint8_t)MIN(abs(delta), 3);
+ if (delta < 0)
+ cmd.gain[i] |= 1 << 2; /* sign bit */
+ sc->sc_ant->gain[i].value.ul = cmd.gain[i];
+ }
+ }
+ return iwn_cmd(sc, IWN_CMD_PHY_CALIB, &cmd, sizeof cmd, 1);
+}
+
+static int
+iwn5000_set_gains(struct iwn_softc *sc)
+{
+ struct iwn_calib_state *calib = &sc->calib;
+ struct iwn_phy_calib_gain cmd;
+ int i, ant, div, delta;
+
+ /* We collected 20 beacons and !=6050 need a 1.5 factor. */
+ div = (sc->hw_type == IWN_HW_REV_TYPE_6050) ? 20 : 30;
+
+ memset(&cmd, 0, sizeof cmd);
+ cmd.code = sc->noise_gain;
+ cmd.ngroups = 1;
+ cmd.isvalid = 1;
+ /* Get first available RX antenna as referential. */
+ ant = IWN_LSB(sc->rxchainmask);
+ /* Set differential gains for other antennas. */
+ for (i = ant + 1; i < 3; i++) {
+ if (sc->chainmask & (1 << i)) {
+ /* The delta is relative to antenna "ant". */
+ delta = (calib->noise[ant] - calib->noise[i]) / div;
+ /* Limit to [-4.5dB,+4.5dB]. */
+ cmd.gain[i - 1] = (uint8_t)MIN(abs(delta), 3);
+ if (delta < 0)
+ cmd.gain[i - 1] |= 1 << 2; /* sign bit */
+ sc->sc_ant->gain[i - 1].value.ul
+ = cmd.gain[i - 1];
+ }
+ }
+ return iwn_cmd(sc, IWN_CMD_PHY_CALIB, &cmd, sizeof cmd, 1);
+}
+
+/*
+ * Tune RF RX sensitivity based on the number of false alarms detected
+ * during the last beacon period.
+ */
+static void
+iwn_tune_sensitivity(struct iwn_softc *sc, const struct iwn_rx_stats *stats)
+{
+#define inc(val, inc, max) \
+ if ((val) < (max)) { \
+ if ((val) < (max) - (inc)) \
+ (val) += (inc); \
+ else \
+ (val) = (max); \
+ needs_update = 1; \
+ }
+#define dec(val, dec, min) \
+ if ((val) > (min)) { \
+ if ((val) > (min) + (dec)) \
+ (val) -= (dec); \
+ else \
+ (val) = (min); \
+ needs_update = 1; \
+ }
+
+ const struct iwn_sensitivity_limits *limits = sc->limits;
+ struct iwn_calib_state *calib = &sc->calib;
+ uint32_t val, rxena, fa;
+ uint32_t energy[3], energy_min;
+ uint8_t noise[3], noise_ref;
+ int i, needs_update = 0;
+
+ /* Check that we've been enabled long enough. */
+ if ((rxena = le32toh(stats->general.load)) == 0)
+ return;
+
+ /* Compute number of false alarms since last call for OFDM. */
+ fa = le32toh(stats->ofdm.bad_plcp) - calib->bad_plcp_ofdm;
+ fa += le32toh(stats->ofdm.fa) - calib->fa_ofdm;
+ fa *= 200 * 1024; /* 200TU */
+
+ /* Save counters values for next call. */
+ calib->bad_plcp_ofdm = le32toh(stats->ofdm.bad_plcp);
+ calib->fa_ofdm = le32toh(stats->ofdm.fa);
+
+ if (fa > 50 * rxena) {
+ /* High false alarm count, decrease sensitivity. */
+ IWN_DBG("OFDM high false alarm count: %u", fa);
+ inc(calib->ofdm_x1, 1, limits->max_ofdm_x1);
+ inc(calib->ofdm_mrc_x1, 1, limits->max_ofdm_mrc_x1);
+ inc(calib->ofdm_x4, 1, limits->max_ofdm_x4);
+ inc(calib->ofdm_mrc_x4, 1, limits->max_ofdm_mrc_x4);
+
+ } else if (fa < 5 * rxena) {
+ /* Low false alarm count, increase sensitivity. */
+ IWN_DBG("OFDM low false alarm count: %u", fa);
+ dec(calib->ofdm_x1, 1, limits->min_ofdm_x1);
+ dec(calib->ofdm_mrc_x1, 1, limits->min_ofdm_mrc_x1);
+ dec(calib->ofdm_x4, 1, limits->min_ofdm_x4);
+ dec(calib->ofdm_mrc_x4, 1, limits->min_ofdm_mrc_x4);
+ }
+
+ /* Compute maximum noise among 3 receivers. */
+ for (i = 0; i < 3; i++)
+ noise[i] = (le32toh(stats->general.noise[i]) >> 8) & 0xff;
+ val = MAX(noise[0], noise[1]);
+ val = MAX(noise[2], val);
+ /* Insert it into our samples table. */
+ calib->noise_samples[calib->cur_noise_sample] = (uint8_t)val;
+ calib->cur_noise_sample = (calib->cur_noise_sample + 1) % 20;
+
+ /* Compute maximum noise among last 20 samples. */
+ noise_ref = calib->noise_samples[0];
+ for (i = 1; i < 20; i++)
+ noise_ref = MAX(noise_ref, calib->noise_samples[i]);
+
+ /* Compute maximum energy among 3 receivers. */
+ for (i = 0; i < 3; i++)
+ energy[i] = le32toh(stats->general.energy[i]);
+ val = MIN(energy[0], energy[1]);
+ val = MIN(energy[2], val);
+ /* Insert it into our samples table. */
+ calib->energy_samples[calib->cur_energy_sample] = val;
+ calib->cur_energy_sample = (calib->cur_energy_sample + 1) % 10;
+
+ /* Compute minimum energy among last 10 samples. */
+ energy_min = calib->energy_samples[0];
+ for (i = 1; i < 10; i++)
+ energy_min = MAX(energy_min, calib->energy_samples[i]);
+ energy_min += 6;
+
+ /* Compute number of false alarms since last call for CCK. */
+ fa = le32toh(stats->cck.bad_plcp) - calib->bad_plcp_cck;
+ fa += le32toh(stats->cck.fa) - calib->fa_cck;
+ fa *= 200 * 1024; /* 200TU */
+
+ /* Save counters values for next call. */
+ calib->bad_plcp_cck = le32toh(stats->cck.bad_plcp);
+ calib->fa_cck = le32toh(stats->cck.fa);
+
+ if (fa > 50 * rxena) {
+ /* High false alarm count, decrease sensitivity. */
+ IWN_DBG("CCK high false alarm count: %u", fa);
+ calib->cck_state = IWN_CCK_STATE_HIFA;
+ calib->low_fa = 0;
+
+ if (calib->cck_x4 > 160) {
+ calib->noise_ref = noise_ref;
+ if (calib->energy_cck > 2)
+ dec(calib->energy_cck, 2, energy_min);
+ }
+ if (calib->cck_x4 < 160) {
+ calib->cck_x4 = 161;
+ needs_update = 1;
+ } else
+ inc(calib->cck_x4, 3, limits->max_cck_x4);
+
+ inc(calib->cck_mrc_x4, 3, limits->max_cck_mrc_x4);
+
+ } else if (fa < 5 * rxena) {
+ /* Low false alarm count, increase sensitivity. */
+ IWN_DBG("CCK low false alarm count: %u", fa);
+ calib->cck_state = IWN_CCK_STATE_LOFA;
+ calib->low_fa++;
+
+ if (calib->cck_state != IWN_CCK_STATE_INIT &&
+ (((int32_t)calib->noise_ref - (int32_t)noise_ref) > 2 ||
+ calib->low_fa > 100)) {
+ inc(calib->energy_cck, 2, limits->min_energy_cck);
+ dec(calib->cck_x4, 3, limits->min_cck_x4);
+ dec(calib->cck_mrc_x4, 3, limits->min_cck_mrc_x4);
+ }
+ } else {
+ /* Not worth to increase or decrease sensitivity. */
+ IWN_DBG("CCK normal false alarm count: %u", fa);
+ calib->low_fa = 0;
+ calib->noise_ref = noise_ref;
+
+ if (calib->cck_state == IWN_CCK_STATE_HIFA) {
+ /* Previous interval had many false alarms. */
+ dec(calib->energy_cck, 8, energy_min);
+ }
+ calib->cck_state = IWN_CCK_STATE_INIT;
+ }
+
+ if (needs_update)
+ (void)iwn_send_sensitivity(sc);
+#undef dec
+#undef inc
+}
+
+static int
+iwn_send_sensitivity(struct iwn_softc *sc)
+{
+ struct iwn_calib_state *calib = &sc->calib;
+ struct iwn_enhanced_sensitivity_cmd cmd;
+ int len;
+
+ memset(&cmd, 0, sizeof cmd);
+ len = sizeof (struct iwn_sensitivity_cmd);
+ cmd.which = IWN_SENSITIVITY_WORKTBL;
+ /* OFDM modulation. */
+ cmd.corr_ofdm_x1 = htole16(calib->ofdm_x1);
+ cmd.corr_ofdm_mrc_x1 = htole16(calib->ofdm_mrc_x1);
+ cmd.corr_ofdm_x4 = htole16(calib->ofdm_x4);
+ cmd.corr_ofdm_mrc_x4 = htole16(calib->ofdm_mrc_x4);
+ cmd.energy_ofdm = htole16(sc->limits->energy_ofdm);
+ cmd.energy_ofdm_th = htole16(62);
+ /* CCK modulation. */
+ cmd.corr_cck_x4 = htole16(calib->cck_x4);
+ cmd.corr_cck_mrc_x4 = htole16(calib->cck_mrc_x4);
+ cmd.energy_cck = htole16(calib->energy_cck);
+ /* Barker modulation: use default values. */
+ cmd.corr_barker = htole16(190);
+ cmd.corr_barker_mrc = htole16(390);
+ if (!(sc->sc_flags & IWN_FLAG_ENH_SENS))
+ goto send;
+ /* Enhanced sensitivity settings. */
+ len = sizeof (struct iwn_enhanced_sensitivity_cmd);
+ cmd.ofdm_det_slope_mrc = htole16(668);
+ cmd.ofdm_det_icept_mrc = htole16(4);
+ cmd.ofdm_det_slope = htole16(486);
+ cmd.ofdm_det_icept = htole16(37);
+ cmd.cck_det_slope_mrc = htole16(853);
+ cmd.cck_det_icept_mrc = htole16(4);
+ cmd.cck_det_slope = htole16(476);
+ cmd.cck_det_icept = htole16(99);
+send:
+
+ sc->sc_sens->ofdm_x1.value.ul = calib->ofdm_x1;
+ sc->sc_sens->ofdm_mrc_x1.value.ul = calib->ofdm_mrc_x1;
+ sc->sc_sens->ofdm_x4.value.ul = calib->ofdm_x4;
+ sc->sc_sens->ofdm_mrc_x4.value.ul = calib->ofdm_mrc_x4;
+ sc->sc_sens->cck_x4.value.ul = calib->cck_x4;
+ sc->sc_sens->cck_mrc_x4.value.ul = calib->cck_mrc_x4;
+ sc->sc_sens->energy_cck.value.ul = calib->energy_cck;
+
+ return iwn_cmd(sc, IWN_CMD_SET_SENSITIVITY, &cmd, len, 1);
+}
+
+/*
+ * Set STA mode power saving level (between 0 and 5).
+ * Level 0 is CAM (Continuously Aware Mode), 5 is for maximum power saving.
+ */
+static int
+iwn_set_pslevel(struct iwn_softc *sc, int dtim, int level, int async)
+{
+ struct iwn_pmgt_cmd cmd;
+ const struct iwn_pmgt *pmgt;
+ uint32_t maxp, skip_dtim;
+ uint32_t reg;
+ int i;
+
+ /* Select which PS parameters to use. */
+ if (dtim <= 2)
+ pmgt = &iwn_pmgt[0][level];
+ else if (dtim <= 10)
+ pmgt = &iwn_pmgt[1][level];
+ else
+ pmgt = &iwn_pmgt[2][level];
+
+ memset(&cmd, 0, sizeof cmd);
+ if (level != 0) /* not CAM */
+ cmd.flags |= htole16(IWN_PS_ALLOW_SLEEP);
+ if (level == 5)
+ cmd.flags |= htole16(IWN_PS_FAST_PD);
+ /* Retrieve PCIe Active State Power Management (ASPM). */
+ reg = pci_config_get32(sc->sc_pcih,
+ sc->sc_cap_off + PCIE_LINKCTL);
+ if (!(reg & PCIE_LINKCTL_ASPM_CTL_L0S)) /* L0s Entry disabled. */
+ cmd.flags |= htole16(IWN_PS_PCI_PMGT);
+ cmd.rxtimeout = htole32(pmgt->rxtimeout * 1024);
+ cmd.txtimeout = htole32(pmgt->txtimeout * 1024);
+
+ if (dtim == 0) {
+ dtim = 1;
+ skip_dtim = 0;
+ } else
+ skip_dtim = pmgt->skip_dtim;
+ if (skip_dtim != 0) {
+ cmd.flags |= htole16(IWN_PS_SLEEP_OVER_DTIM);
+ maxp = pmgt->intval[4];
+ if (maxp == (uint32_t)-1)
+ maxp = dtim * (skip_dtim + 1);
+ else if (maxp > dtim)
+ maxp = (maxp / dtim) * dtim;
+ } else
+ maxp = dtim;
+ for (i = 0; i < 5; i++)
+ cmd.intval[i] = htole32(MIN(maxp, pmgt->intval[i]));
+
+ sc->sc_misc->pslevel.value.ul = level;
+ return iwn_cmd(sc, IWN_CMD_SET_POWER_MODE, &cmd, sizeof cmd, async);
+}
+
+int
+iwn5000_runtime_calib(struct iwn_softc *sc)
+{
+ struct iwn5000_calib_config cmd;
+
+ memset(&cmd, 0, sizeof cmd);
+ cmd.ucode.once.enable = 0xffffffff;
+ cmd.ucode.once.start = IWN5000_CALIB_DC;
+ return iwn_cmd(sc, IWN5000_CMD_CALIB_CONFIG, &cmd, sizeof(cmd), 0);
+}
+
+static int
+iwn_config_bt_coex_bluetooth(struct iwn_softc *sc)
+{
+ struct iwn_bluetooth bluetooth;
+
+ memset(&bluetooth, 0, sizeof bluetooth);
+ bluetooth.flags = IWN_BT_COEX_ENABLE;
+ bluetooth.lead_time = IWN_BT_LEAD_TIME_DEF;
+ bluetooth.max_kill = IWN_BT_MAX_KILL_DEF;
+
+ return iwn_cmd(sc, IWN_CMD_BT_COEX, &bluetooth, sizeof bluetooth, 0);
+}
+
+static int
+iwn_config_bt_coex_prio_table(struct iwn_softc *sc)
+{
+ uint8_t prio_table[16];
+
+ memset(&prio_table, 0, sizeof prio_table);
+ prio_table[ 0] = 6; /* init calibration 1 */
+ prio_table[ 1] = 7; /* init calibration 2 */
+ prio_table[ 2] = 2; /* periodic calib low 1 */
+ prio_table[ 3] = 3; /* periodic calib low 2 */
+ prio_table[ 4] = 4; /* periodic calib high 1 */
+ prio_table[ 5] = 5; /* periodic calib high 2 */
+ prio_table[ 6] = 6; /* dtim */
+ prio_table[ 7] = 8; /* scan52 */
+ prio_table[ 8] = 10; /* scan24 */
+
+ return iwn_cmd(sc, IWN_CMD_BT_COEX_PRIO_TABLE,
+ &prio_table, sizeof prio_table, 0);
+}
+
+static int
+iwn_config_bt_coex_adv_config(struct iwn_softc *sc, struct iwn_bt_basic *basic,
+ size_t len)
+{
+ struct iwn_btcoex_prot btprot;
+ int error;
+
+ basic->bt.flags = IWN_BT_COEX_ENABLE;
+ basic->bt.lead_time = IWN_BT_LEAD_TIME_DEF;
+ basic->bt.max_kill = IWN_BT_MAX_KILL_DEF;
+ basic->bt.bt3_timer_t7_value = IWN_BT_BT3_T7_DEF;
+ basic->bt.kill_ack_mask = IWN_BT_KILL_ACK_MASK_DEF;
+ basic->bt.kill_cts_mask = IWN_BT_KILL_CTS_MASK_DEF;
+ basic->bt3_prio_sample_time = IWN_BT_BT3_PRIO_SAMPLE_DEF;
+ basic->bt3_timer_t2_value = IWN_BT_BT3_T2_DEF;
+ basic->bt3_lookup_table[ 0] = htole32(0xaaaaaaaa); /* Normal */
+ basic->bt3_lookup_table[ 1] = htole32(0xaaaaaaaa);
+ basic->bt3_lookup_table[ 2] = htole32(0xaeaaaaaa);
+ basic->bt3_lookup_table[ 3] = htole32(0xaaaaaaaa);
+ basic->bt3_lookup_table[ 4] = htole32(0xcc00ff28);
+ basic->bt3_lookup_table[ 5] = htole32(0x0000aaaa);
+ basic->bt3_lookup_table[ 6] = htole32(0xcc00aaaa);
+ basic->bt3_lookup_table[ 7] = htole32(0x0000aaaa);
+ basic->bt3_lookup_table[ 8] = htole32(0xc0004000);
+ basic->bt3_lookup_table[ 9] = htole32(0x00004000);
+ basic->bt3_lookup_table[10] = htole32(0xf0005000);
+ basic->bt3_lookup_table[11] = htole32(0xf0005000);
+ basic->reduce_txpower = 0; /* as not implemented */
+ basic->valid = IWN_BT_ALL_VALID_MASK;
+
+ error = iwn_cmd(sc, IWN_CMD_BT_COEX, &basic, len, 0);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not configure advanced bluetooth coexistence");
+ return error;
+ }
+
+ error = iwn_config_bt_coex_prio_table(sc);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not configure send BT priority table");
+ return error;
+ }
+
+ /* Force BT state machine change */
+ memset(&btprot, 0, sizeof btprot);
+ btprot.open = 1;
+ btprot.type = 1;
+ error = iwn_cmd(sc, IWN_CMD_BT_COEX_PROT, &btprot, sizeof btprot, 1);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN, "!could not open BT protcol");
+ return error;
+ }
+
+ btprot.open = 0;
+ error = iwn_cmd(sc, IWN_CMD_BT_COEX_PROT, &btprot, sizeof btprot, 1);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN, "!could not close BT protcol");
+ return error;
+ }
+ return 0;
+}
+
+static int
+iwn_config_bt_coex_adv1(struct iwn_softc *sc)
+{
+ struct iwn_bt_adv1 d;
+
+ memset(&d, 0, sizeof d);
+ d.prio_boost = IWN_BT_PRIO_BOOST_DEF;
+ d.tx_prio_boost = 0;
+ d.rx_prio_boost = 0;
+ return iwn_config_bt_coex_adv_config(sc, &d.basic, sizeof d);
+}
+
+static int
+iwn_config_bt_coex_adv2(struct iwn_softc *sc)
+{
+ struct iwn_bt_adv2 d;
+
+ memset(&d, 0, sizeof d);
+ d.prio_boost = IWN_BT_PRIO_BOOST_DEF;
+ d.tx_prio_boost = 0;
+ d.rx_prio_boost = 0;
+ return iwn_config_bt_coex_adv_config(sc, &d.basic, sizeof d);
+}
+
+static int
+iwn_config(struct iwn_softc *sc)
+{
+ struct iwn_ops *ops = &sc->ops;
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint32_t txmask;
+ uint16_t rxchain;
+ int error;
+
+ error = ops->config_bt_coex(sc);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not configure bluetooth coexistence");
+ return error;
+ }
+
+ /* Set radio temperature sensor offset. */
+ if (sc->hw_type == IWN_HW_REV_TYPE_6005) {
+ error = iwn6000_temp_offset_calib(sc);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not set temperature offset");
+ return error;
+ }
+ }
+
+ if (sc->hw_type == IWN_HW_REV_TYPE_2030 ||
+ sc->hw_type == IWN_HW_REV_TYPE_2000 ||
+ sc->hw_type == IWN_HW_REV_TYPE_135 ||
+ sc->hw_type == IWN_HW_REV_TYPE_105) {
+ error = iwn2000_temp_offset_calib(sc);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not set temperature offset");
+ return error;
+ }
+ }
+
+ if (sc->hw_type == IWN_HW_REV_TYPE_6050 ||
+ sc->hw_type == IWN_HW_REV_TYPE_6005) {
+ /* Configure runtime DC calibration. */
+ error = iwn5000_runtime_calib(sc);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not configure runtime calibration");
+ return error;
+ }
+ }
+
+ /* Configure valid TX chains for 5000 Series. */
+ if (sc->hw_type != IWN_HW_REV_TYPE_4965) {
+ txmask = htole32(sc->txchainmask);
+ error = iwn_cmd(sc, IWN5000_CMD_TX_ANT_CONFIG, &txmask,
+ sizeof txmask, 0);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not configure valid TX chains");
+ return error;
+ }
+ }
+
+ /* Set mode, channel, RX filter and enable RX. */
+ memset(&sc->rxon, 0, sizeof (struct iwn_rxon));
+ IEEE80211_ADDR_COPY(sc->rxon.myaddr, ic->ic_macaddr);
+ IEEE80211_ADDR_COPY(sc->rxon.wlap, ic->ic_macaddr);
+ sc->rxon.chan = ieee80211_chan2ieee(ic, ic->ic_ibss_chan);
+ sc->rxon.flags = htole32(IWN_RXON_TSF | IWN_RXON_CTS_TO_SELF);
+ if (IEEE80211_IS_CHAN_2GHZ(ic->ic_ibss_chan))
+ sc->rxon.flags |= htole32(IWN_RXON_AUTO | IWN_RXON_24GHZ);
+ switch (ic->ic_opmode) {
+ case IEEE80211_M_IBSS:
+ sc->rxon.mode = IWN_MODE_IBSS;
+ sc->rxon.filter = htole32(IWN_FILTER_MULTICAST);
+ break;
+ case IEEE80211_M_STA:
+ sc->rxon.mode = IWN_MODE_STA;
+ sc->rxon.filter = htole32(IWN_FILTER_MULTICAST);
+ break;
+ case IEEE80211_M_MONITOR:
+ sc->rxon.mode = IWN_MODE_MONITOR;
+ sc->rxon.filter = htole32(IWN_FILTER_MULTICAST |
+ IWN_FILTER_CTL | IWN_FILTER_PROMISC);
+ break;
+ default:
+ /* Should not get there. */
+ ASSERT(ic->ic_opmode == IEEE80211_M_IBSS ||
+ ic->ic_opmode == IEEE80211_M_STA ||
+ ic->ic_opmode == IEEE80211_M_MONITOR);
+ break;
+ }
+ sc->rxon.cck_mask = 0x0f; /* not yet negotiated */
+ sc->rxon.ofdm_mask = 0xff; /* not yet negotiated */
+ sc->rxon.ht_single_mask = 0xff;
+ sc->rxon.ht_dual_mask = 0xff;
+ sc->rxon.ht_triple_mask = 0xff;
+ rxchain =
+ IWN_RXCHAIN_VALID(sc->rxchainmask) |
+ IWN_RXCHAIN_MIMO_COUNT(2) |
+ IWN_RXCHAIN_IDLE_COUNT(2);
+ sc->rxon.rxchain = htole16(rxchain);
+ DTRACE_PROBE2(rxon, struct iwn_rxon *, &sc->rxon, int, sc->rxonsz);
+ error = iwn_cmd(sc, IWN_CMD_RXON, &sc->rxon, sc->rxonsz, 0);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!RXON command failed");
+ return error;
+ }
+
+ if ((error = iwn_add_broadcast_node(sc, 0)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not add broadcast node");
+ return error;
+ }
+
+ /* Configuration has changed, set TX power accordingly. */
+ if ((error = ops->set_txpower(sc, 0)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not set TX power");
+ return error;
+ }
+
+ if ((error = iwn_set_critical_temp(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not set critical temperature");
+ return error;
+ }
+
+ /* Set power saving level to CAM during initialization. */
+ if ((error = iwn_set_pslevel(sc, 0, 0, 0)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not set power saving level");
+ return error;
+ }
+ return 0;
+}
+
+static uint16_t
+iwn_get_active_dwell_time(struct iwn_softc *sc, uint16_t flags,
+ uint8_t n_probes)
+{
+ _NOTE(ARGUNUSED(sc));
+
+ /* No channel? Default to 2GHz settings */
+ if (flags & IEEE80211_CHAN_2GHZ)
+ return IWN_ACTIVE_DWELL_TIME_2GHZ +
+ IWN_ACTIVE_DWELL_FACTOR_2GHZ * (n_probes + 1);
+
+ /* 5GHz dwell time */
+ return IWN_ACTIVE_DWELL_TIME_5GHZ +
+ IWN_ACTIVE_DWELL_FACTOR_5GHZ * (n_probes + 1);
+}
+
+/*
+ * Limit the total dwell time to 85% of the beacon interval.
+ *
+ * Returns the dwell time in milliseconds.
+ */
+static uint16_t
+iwn_limit_dwell(struct iwn_softc *sc, uint16_t dwell_time)
+{
+ _NOTE(ARGUNUSED(dwell_time));
+
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_node *ni = ic->ic_bss;
+ int bintval = 0;
+
+ /* bintval is in TU (1.024mS) */
+ if (ni != NULL)
+ bintval = ni->in_intval;
+
+ /*
+ * If it's non-zero, we should calculate the minimum of
+ * it and the DWELL_BASE.
+ *
+ * XXX Yes, the math should take into account that bintval
+ * is 1.024mS, not 1mS..
+ */
+ if (bintval > 0)
+ return MIN(IWN_PASSIVE_DWELL_BASE, ((bintval * 85) / 100));
+
+ /* No association context? Default */
+ return IWN_PASSIVE_DWELL_BASE;
+}
+
+static uint16_t
+iwn_get_passive_dwell_time(struct iwn_softc *sc, uint16_t flags)
+{
+ uint16_t passive;
+ if (flags & IEEE80211_CHAN_2GHZ)
+ passive = IWN_PASSIVE_DWELL_BASE + IWN_PASSIVE_DWELL_TIME_2GHZ;
+ else
+ passive = IWN_PASSIVE_DWELL_BASE + IWN_PASSIVE_DWELL_TIME_5GHZ;
+
+ /* Clamp to the beacon interval if we're associated */
+ return iwn_limit_dwell(sc, passive);
+}
+
+static int
+iwn_scan(struct iwn_softc *sc, uint16_t flags)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct iwn_scan_hdr *hdr;
+ struct iwn_cmd_data *tx;
+ struct iwn_scan_essid *essid;
+ struct iwn_scan_chan *chan;
+ struct ieee80211_frame *wh;
+ struct ieee80211_rateset *rs;
+ struct ieee80211_channel *c;
+ uint8_t *buf, *frm;
+ uint16_t rxchain, dwell_active, dwell_passive;
+ uint8_t txant;
+ int buflen, error, is_active;
+
+ buf = kmem_zalloc(IWN_SCAN_MAXSZ, KM_NOSLEEP);
+ if (buf == NULL) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not allocate buffer for scan command");
+ return ENOMEM;
+ }
+ hdr = (struct iwn_scan_hdr *)buf;
+ /*
+ * Move to the next channel if no frames are received within 20ms
+ * after sending the probe request.
+ */
+ hdr->quiet_time = htole16(20); /* timeout in milliseconds */
+ hdr->quiet_threshold = htole16(1); /* min # of packets */
+
+ /* Select antennas for scanning. */
+ rxchain =
+ IWN_RXCHAIN_VALID(sc->rxchainmask) |
+ IWN_RXCHAIN_FORCE_MIMO_SEL(sc->rxchainmask) |
+ IWN_RXCHAIN_DRIVER_FORCE;
+ if ((flags & IEEE80211_CHAN_5GHZ) &&
+ sc->hw_type == IWN_HW_REV_TYPE_4965) {
+ /* Ant A must be avoided in 5GHz because of an HW bug. */
+ rxchain |= IWN_RXCHAIN_FORCE_SEL(IWN_ANT_BC);
+ } else /* Use all available RX antennas. */
+ rxchain |= IWN_RXCHAIN_FORCE_SEL(sc->rxchainmask);
+ hdr->rxchain = htole16(rxchain);
+ hdr->filter = htole32(IWN_FILTER_MULTICAST | IWN_FILTER_BEACON);
+
+ tx = (struct iwn_cmd_data *)(hdr + 1);
+ tx->flags = htole32(IWN_TX_AUTO_SEQ);
+ tx->id = sc->broadcast_id;
+ tx->lifetime = htole32(IWN_LIFETIME_INFINITE);
+
+ if (flags & IEEE80211_CHAN_5GHZ) {
+ /* Send probe requests at 6Mbps. */
+ tx->plcp = iwn_rates[IWN_RIDX_OFDM6].plcp;
+ rs = &ic->ic_sup_rates[IEEE80211_MODE_11A];
+ } else {
+ hdr->flags = htole32(IWN_RXON_24GHZ | IWN_RXON_AUTO);
+ /* Send probe requests at 1Mbps. */
+ tx->plcp = iwn_rates[IWN_RIDX_CCK1].plcp;
+ tx->rflags = IWN_RFLAG_CCK;
+ rs = &ic->ic_sup_rates[IEEE80211_MODE_11G];
+ }
+
+ hdr->crc_threshold = 0xffff;
+
+ /* Use the first valid TX antenna. */
+ txant = IWN_LSB(sc->txchainmask);
+ tx->rflags |= IWN_RFLAG_ANT(txant);
+
+ /*
+ * Only do active scanning if we're announcing a probe request
+ * for a given SSID (or more, if we ever add it to the driver.)
+ */
+ is_active = 0;
+
+ essid = (struct iwn_scan_essid *)(tx + 1);
+ if (ic->ic_des_esslen != 0) {
+ char essidstr[IEEE80211_NWID_LEN+1];
+ memcpy(essidstr, ic->ic_des_essid, ic->ic_des_esslen);
+ essidstr[ic->ic_des_esslen] = '\0';
+
+ DTRACE_PROBE1(scan__direct, char *, essidstr);
+
+ essid[0].id = IEEE80211_ELEMID_SSID;
+ essid[0].len = ic->ic_des_esslen;
+ memcpy(essid[0].data, ic->ic_des_essid, ic->ic_des_esslen);
+
+ is_active = 1;
+ /* hdr->crc_threshold = 0x1; */
+ hdr->scan_flags = htole32(IWN_SCAN_PASSIVE2ACTIVE);
+ }
+ /*
+ * Build a probe request frame. Most of the following code is a
+ * copy & paste of what is done in net80211.
+ */
+ wh = (struct ieee80211_frame *)(essid + 20);
+ wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT |
+ IEEE80211_FC0_SUBTYPE_PROBE_REQ;
+ wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
+ IEEE80211_ADDR_COPY(wh->i_addr1, etherbroadcastaddr);
+ IEEE80211_ADDR_COPY(wh->i_addr2, ic->ic_macaddr);
+ IEEE80211_ADDR_COPY(wh->i_addr3, etherbroadcastaddr);
+ wh->i_dur[0] = wh->i_dur[1] = 0; /* filled by HW */
+ wh->i_seq[0] = wh->i_seq[1] = 0; /* filled by HW */
+
+ frm = (uint8_t *)(wh + 1);
+ frm = ieee80211_add_ssid(frm, ic->ic_des_essid, ic->ic_des_esslen);
+ frm = ieee80211_add_rates(frm, rs);
+#ifndef IEEE80211_NO_HT
+ if (ic->ic_flags & IEEE80211_F_HTON)
+ frm = ieee80211_add_htcaps(frm, ic);
+#endif
+ if (rs->ir_nrates > IEEE80211_RATE_SIZE)
+ frm = ieee80211_add_xrates(frm, rs);
+
+ /* Set length of probe request. */
+ /*LINTED: E_PTRDIFF_OVERFLOW*/
+ tx->len = htole16(frm - (uint8_t *)wh);
+
+
+ /*
+ * If active scanning is requested but a certain channel is
+ * marked passive, we can do active scanning if we detect
+ * transmissions.
+ *
+ * There is an issue with some firmware versions that triggers
+ * a sysassert on a "good CRC threshold" of zero (== disabled),
+ * on a radar channel even though this means that we should NOT
+ * send probes.
+ *
+ * The "good CRC threshold" is the number of frames that we
+ * need to receive during our dwell time on a channel before
+ * sending out probes -- setting this to a huge value will
+ * mean we never reach it, but at the same time work around
+ * the aforementioned issue. Thus use IWN_GOOD_CRC_TH_NEVER
+ * here instead of IWN_GOOD_CRC_TH_DISABLED.
+ *
+ * This was fixed in later versions along with some other
+ * scan changes, and the threshold behaves as a flag in those
+ * versions.
+ */
+
+ /*
+ * If we're doing active scanning, set the crc_threshold
+ * to a suitable value. This is different to active veruss
+ * passive scanning depending upon the channel flags; the
+ * firmware will obey that particular check for us.
+ */
+ if (sc->tlv_feature_flags & IWN_UCODE_TLV_FLAGS_NEWSCAN)
+ hdr->crc_threshold = is_active ?
+ IWN_GOOD_CRC_TH_DEFAULT : IWN_GOOD_CRC_TH_DISABLED;
+ else
+ hdr->crc_threshold = is_active ?
+ IWN_GOOD_CRC_TH_DEFAULT : IWN_GOOD_CRC_TH_NEVER;
+
+ chan = (struct iwn_scan_chan *)frm;
+ for (c = &ic->ic_sup_channels[1];
+ c <= &ic->ic_sup_channels[IEEE80211_CHAN_MAX]; c++) {
+ if ((c->ich_flags & flags) != flags)
+ continue;
+ chan->chan = htole16(ieee80211_chan2ieee(ic, c));
+ chan->flags = 0;
+ if (!(c->ich_flags & IEEE80211_CHAN_PASSIVE))
+ chan->flags |= htole32(IWN_CHAN_ACTIVE);
+ if (ic->ic_des_esslen != 0)
+ chan->flags |= htole32(IWN_CHAN_NPBREQS(1));
+
+ /*
+ * Calculate the active/passive dwell times.
+ */
+
+ dwell_active = iwn_get_active_dwell_time(sc, flags, is_active);
+ dwell_passive = iwn_get_passive_dwell_time(sc, flags);
+
+ /* Make sure they're valid */
+ if (dwell_passive <= dwell_active)
+ dwell_passive = dwell_active + 1;
+
+ chan->active = htole16(dwell_active);
+ chan->passive = htole16(dwell_passive);
+
+ chan->dsp_gain = 0x6e;
+ if (IEEE80211_IS_CHAN_5GHZ(c)) {
+ chan->rf_gain = 0x3b;
+ } else {
+ chan->rf_gain = 0x28;
+ }
+ DTRACE_PROBE5(add__channel, uint8_t, chan->chan,
+ uint32_t, chan->flags, uint8_t, chan->rf_gain,
+ uint16_t, chan->active, uint16_t, chan->passive);
+ hdr->nchan++;
+ chan++;
+ }
+
+ /*LINTED: E_PTRDIFF_OVERFLOW*/
+ buflen = (uint8_t *)chan - buf;
+ hdr->len = htole16(buflen);
+
+ error = iwn_cmd(sc, IWN_CMD_SCAN, buf, buflen, 1);
+ kmem_free(buf, IWN_SCAN_MAXSZ);
+ return error;
+}
+
+static int
+iwn_auth(struct iwn_softc *sc)
+{
+ struct iwn_ops *ops = &sc->ops;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_node *ni = ic->ic_bss;
+ int error;
+
+ ASSERT(ni->in_chan != NULL);
+
+ /* Update adapter configuration. */
+ IEEE80211_ADDR_COPY(sc->rxon.bssid, ni->in_bssid);
+ sc->rxon.chan = ieee80211_chan2ieee(ic, ni->in_chan);
+ sc->rxon.flags = htole32(IWN_RXON_TSF | IWN_RXON_CTS_TO_SELF);
+ if ((ni->in_chan != IEEE80211_CHAN_ANYC) &&
+ IEEE80211_IS_CHAN_2GHZ(ni->in_chan))
+ sc->rxon.flags |= htole32(IWN_RXON_AUTO | IWN_RXON_24GHZ);
+ if (ic->ic_flags & IEEE80211_F_SHSLOT)
+ sc->rxon.flags |= htole32(IWN_RXON_SHSLOT);
+ if (ic->ic_flags & IEEE80211_F_SHPREAMBLE)
+ sc->rxon.flags |= htole32(IWN_RXON_SHPREAMBLE);
+ switch (ic->ic_curmode) {
+ case IEEE80211_MODE_11A:
+ sc->rxon.cck_mask = 0;
+ sc->rxon.ofdm_mask = 0x15;
+ break;
+ case IEEE80211_MODE_11B:
+ sc->rxon.cck_mask = 0x03;
+ sc->rxon.ofdm_mask = 0;
+ break;
+ default: /* Assume 802.11b/g. */
+ sc->rxon.cck_mask = 0x0f;
+ sc->rxon.ofdm_mask = 0x15;
+ }
+ DTRACE_PROBE2(rxon, struct iwn_rxon *, &sc->rxon, int, sc->rxonsz);
+ error = iwn_cmd(sc, IWN_CMD_RXON, &sc->rxon, sc->rxonsz, 1);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!RXON command failed");
+ return error;
+ }
+
+ /* Configuration has changed, set TX power accordingly. */
+ if ((error = ops->set_txpower(sc, 1)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not set TX power");
+ return error;
+ }
+ /*
+ * Reconfiguring RXON clears the firmware nodes table so we must
+ * add the broadcast node again.
+ */
+ if ((error = iwn_add_broadcast_node(sc, 1)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not add broadcast node");
+ return error;
+ }
+ return 0;
+}
+
+static int
+iwn_fast_recover(struct iwn_softc *sc)
+{
+ int err = IWN_FAIL;
+
+ mutex_enter(&sc->sc_mtx);
+
+ /* restore runtime configuration */
+ bcopy(&sc->rxon_save, &sc->rxon,
+ sizeof (sc->rxon));
+
+ sc->rxon.associd = 0;
+ sc->rxon.filter &= ~htole32(IWN_FILTER_BSS);
+
+ if ((err = iwn_auth(sc)) != IWN_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN, "!iwn_fast_recover(): "
+ "could not setup authentication");
+ mutex_exit(&sc->sc_mtx);
+ return (err);
+ }
+
+ bcopy(&sc->rxon_save, &sc->rxon, sizeof (sc->rxon));
+
+ /* update adapter's configuration */
+ err = iwn_run(sc);
+ if (err != IWN_SUCCESS) {
+ dev_err(sc->sc_dip, CE_WARN, "!iwn_fast_recover(): "
+ "failed to setup association");
+ mutex_exit(&sc->sc_mtx);
+ return (err);
+ }
+ /* set LED on */
+ iwn_set_led(sc, IWN_LED_LINK, 0, 1);
+
+ sc->sc_flags &= ~IWN_FLAG_HW_ERR_RECOVER;
+ mutex_exit(&sc->sc_mtx);
+
+ /* start queue */
+ DTRACE_PROBE(resume__xmit);
+
+ return (IWN_SUCCESS);
+}
+
+static int
+iwn_run(struct iwn_softc *sc)
+{
+ struct iwn_ops *ops = &sc->ops;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_node *ni = ic->ic_bss;
+ struct iwn_node_info node;
+ int error;
+
+ if (ic->ic_opmode == IEEE80211_M_MONITOR) {
+ /* Link LED blinks while monitoring. */
+ iwn_set_led(sc, IWN_LED_LINK, 5, 5);
+ return 0;
+ }
+ if ((error = iwn_set_timing(sc, ni)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not set timing");
+ return error;
+ }
+
+ /* Update adapter configuration. */
+ IEEE80211_ADDR_COPY(sc->rxon.bssid, ni->in_bssid);
+ sc->rxon.associd = htole16(IEEE80211_AID(ni->in_associd));
+ /* Short preamble and slot time are negotiated when associating. */
+ sc->rxon.flags &= ~htole32(IWN_RXON_SHPREAMBLE | IWN_RXON_SHSLOT);
+ if (ic->ic_flags & IEEE80211_F_SHSLOT)
+ sc->rxon.flags |= htole32(IWN_RXON_SHSLOT);
+ if (ic->ic_flags & IEEE80211_F_SHPREAMBLE)
+ sc->rxon.flags |= htole32(IWN_RXON_SHPREAMBLE);
+ sc->rxon.filter |= htole32(IWN_FILTER_BSS);
+ if (ic->ic_opmode != IEEE80211_M_STA &&
+ ic->ic_opmode != IEEE80211_M_IBSS)
+ sc->rxon.filter |= htole32(IWN_FILTER_BEACON);
+ DTRACE_PROBE2(rxon, struct iwn_rxon *, &sc->rxon, int, sc->rxonsz);
+ error = iwn_cmd(sc, IWN_CMD_RXON, &sc->rxon, sc->rxonsz, 1);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not update configuration");
+ return error;
+ }
+
+ /* Configuration has changed, set TX power accordingly. */
+ if ((error = ops->set_txpower(sc, 1)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not set TX power");
+ return error;
+ }
+
+ /* Fake a join to initialize the TX rate. */
+ ((struct iwn_node *)ni)->id = IWN_ID_BSS;
+ iwn_newassoc(ni, 1);
+
+ /* Add BSS node. */
+ memset(&node, 0, sizeof node);
+ IEEE80211_ADDR_COPY(node.macaddr, ni->in_macaddr);
+ node.id = IWN_ID_BSS;
+#ifdef notyet
+ node.htflags = htole32(IWN_AMDPU_SIZE_FACTOR(3) |
+ IWN_AMDPU_DENSITY(5)); /* 2us */
+#endif
+ error = ops->add_node(sc, &node, 1);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not add BSS node");
+ return error;
+ }
+ if ((error = iwn_set_link_quality(sc, ni)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not setup link quality for node %d", node.id);
+ return error;
+ }
+
+ if ((error = iwn_init_sensitivity(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not set sensitivity");
+ return error;
+ }
+
+ if ((error = iwn_qosparam_to_hw(sc, 1)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not set QoS params");
+ return (error);
+ }
+
+ /* Start periodic calibration timer. */
+ sc->sc_flags &= ~IWN_FLAG_STOP_CALIB_TO;
+ sc->calib.state = IWN_CALIB_STATE_ASSOC;
+ sc->calib_cnt = 0;
+ sc->calib_to = timeout(iwn_calib_timeout, sc, drv_usectohz(500000));
+
+ /* Link LED always on while associated. */
+ iwn_set_led(sc, IWN_LED_LINK, 0, 1);
+ return 0;
+}
+
+#ifdef IWN_HWCRYPTO
+/*
+ * We support CCMP hardware encryption/decryption of unicast frames only.
+ * HW support for TKIP really sucks. We should let TKIP die anyway.
+ */
+static int
+iwn_set_key(struct ieee80211com *ic, struct ieee80211_node *ni,
+ struct ieee80211_key *k)
+{
+ struct iwn_softc *sc = ic->ic_softc;
+ struct iwn_ops *ops = &sc->ops;
+ struct iwn_node *wn = (void *)ni;
+ struct iwn_node_info node;
+ uint16_t kflags;
+
+ if ((k->k_flags & IEEE80211_KEY_GROUP) ||
+ k->k_cipher != IEEE80211_CIPHER_CCMP)
+ return ieee80211_set_key(ic, ni, k);
+
+ kflags = IWN_KFLAG_CCMP | IWN_KFLAG_MAP | IWN_KFLAG_KID(k->k_id);
+ if (k->k_flags & IEEE80211_KEY_GROUP)
+ kflags |= IWN_KFLAG_GROUP;
+
+ memset(&node, 0, sizeof node);
+ node.id = (k->k_flags & IEEE80211_KEY_GROUP) ?
+ sc->broadcast_id : wn->id;
+ node.control = IWN_NODE_UPDATE;
+ node.flags = IWN_FLAG_SET_KEY;
+ node.kflags = htole16(kflags);
+ node.kid = k->k_id;
+ memcpy(node.key, k->k_key, k->k_len);
+ DTRACE_PROBE2(set__key, int, k->k_id, int, node.id);
+ return ops->add_node(sc, &node, 1);
+}
+
+static void
+iwn_delete_key(struct ieee80211com *ic, struct ieee80211_node *ni,
+ struct ieee80211_key *k)
+{
+ struct iwn_softc *sc = ic->ic_softc;
+ struct iwn_ops *ops = &sc->ops;
+ struct iwn_node *wn = (void *)ni;
+ struct iwn_node_info node;
+
+ if ((k->k_flags & IEEE80211_KEY_GROUP) ||
+ k->k_cipher != IEEE80211_CIPHER_CCMP) {
+ /* See comment about other ciphers above. */
+ ieee80211_delete_key(ic, ni, k);
+ return;
+ }
+ if (ic->ic_state != IEEE80211_S_RUN)
+ return; /* Nothing to do. */
+ memset(&node, 0, sizeof node);
+ node.id = (k->k_flags & IEEE80211_KEY_GROUP) ?
+ sc->broadcast_id : wn->id;
+ node.control = IWN_NODE_UPDATE;
+ node.flags = IWN_FLAG_SET_KEY;
+ node.kflags = htole16(IWN_KFLAG_INVALID);
+ node.kid = 0xff;
+ DTRACE_PROBE1(del__key, int, node.id);
+ (void)ops->add_node(sc, &node, 1);
+}
+#endif
+
+#ifndef IEEE80211_NO_HT
+/*
+ * This function is called by upper layer when an ADDBA request is received
+ * from another STA and before the ADDBA response is sent.
+ */
+static int
+iwn_ampdu_rx_start(struct ieee80211com *ic, struct ieee80211_node *ni,
+ uint8_t tid)
+{
+ struct ieee80211_rx_ba *ba = &ni->in_rx_ba[tid];
+ struct iwn_softc *sc = ic->ic_softc;
+ struct iwn_ops *ops = &sc->ops;
+ struct iwn_node *wn = (void *)ni;
+ struct iwn_node_info node;
+
+ memset(&node, 0, sizeof node);
+ node.id = wn->id;
+ node.control = IWN_NODE_UPDATE;
+ node.flags = IWN_FLAG_SET_ADDBA;
+ node.addba_tid = tid;
+ node.addba_ssn = htole16(ba->ba_winstart);
+ DTRACE_PROBE3(addba, uint8_t, wn->id, uint8_t, tid, int, ba->ba_winstart);
+ return ops->add_node(sc, &node, 1);
+}
+
+/*
+ * This function is called by upper layer on teardown of an HT-immediate
+ * Block Ack agreement (eg. uppon receipt of a DELBA frame).
+ */
+static void
+iwn_ampdu_rx_stop(struct ieee80211com *ic, struct ieee80211_node *ni,
+ uint8_t tid)
+{
+ struct iwn_softc *sc = ic->ic_softc;
+ struct iwn_ops *ops = &sc->ops;
+ struct iwn_node *wn = (void *)ni;
+ struct iwn_node_info node;
+
+ memset(&node, 0, sizeof node);
+ node.id = wn->id;
+ node.control = IWN_NODE_UPDATE;
+ node.flags = IWN_FLAG_SET_DELBA;
+ node.delba_tid = tid;
+ DTRACE_PROBE2(delba, uint8_t, wn->id, uint8_t, tid);
+ (void)ops->add_node(sc, &node, 1);
+}
+
+/*
+ * This function is called by upper layer when an ADDBA response is received
+ * from another STA.
+ */
+static int
+iwn_ampdu_tx_start(struct ieee80211com *ic, struct ieee80211_node *ni,
+ uint8_t tid)
+{
+ struct ieee80211_tx_ba *ba = &ni->in_tx_ba[tid];
+ struct iwn_softc *sc = ic->ic_softc;
+ struct iwn_ops *ops = &sc->ops;
+ struct iwn_node *wn = (void *)ni;
+ struct iwn_node_info node;
+ int error;
+
+ /* Enable TX for the specified RA/TID. */
+ wn->disable_tid &= ~(1 << tid);
+ memset(&node, 0, sizeof node);
+ node.id = wn->id;
+ node.control = IWN_NODE_UPDATE;
+ node.flags = IWN_FLAG_SET_DISABLE_TID;
+ node.disable_tid = htole16(wn->disable_tid);
+ error = ops->add_node(sc, &node, 1);
+ if (error != 0)
+ return error;
+
+ if ((error = iwn_nic_lock(sc)) != 0)
+ return error;
+ ops->ampdu_tx_start(sc, ni, tid, ba->ba_winstart);
+ iwn_nic_unlock(sc);
+ return 0;
+}
+
+static void
+iwn_ampdu_tx_stop(struct ieee80211com *ic, struct ieee80211_node *ni,
+ uint8_t tid)
+{
+ struct ieee80211_tx_ba *ba = &ni->in_tx_ba[tid];
+ struct iwn_softc *sc = ic->ic_softc;
+ struct iwn_ops *ops = &sc->ops;
+
+ if (iwn_nic_lock(sc) != 0)
+ return;
+ ops->ampdu_tx_stop(sc, tid, ba->ba_winstart);
+ iwn_nic_unlock(sc);
+}
+
+static void
+iwn4965_ampdu_tx_start(struct iwn_softc *sc, struct ieee80211_node *ni,
+ uint8_t tid, uint16_t ssn)
+{
+ struct iwn_node *wn = (void *)ni;
+ int qid = 7 + tid;
+
+ /* Stop TX scheduler while we're changing its configuration. */
+ iwn_prph_write(sc, IWN4965_SCHED_QUEUE_STATUS(qid),
+ IWN4965_TXQ_STATUS_CHGACT);
+
+ /* Assign RA/TID translation to the queue. */
+ iwn_mem_write_2(sc, sc->sched_base + IWN4965_SCHED_TRANS_TBL(qid),
+ wn->id << 4 | tid);
+
+ /* Enable chain-building mode for the queue. */
+ iwn_prph_setbits(sc, IWN4965_SCHED_QCHAIN_SEL, 1 << qid);
+
+ /* Set starting sequence number from the ADDBA request. */
+ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | (ssn & 0xff));
+ iwn_prph_write(sc, IWN4965_SCHED_QUEUE_RDPTR(qid), ssn);
+
+ /* Set scheduler window size. */
+ iwn_mem_write(sc, sc->sched_base + IWN4965_SCHED_QUEUE_OFFSET(qid),
+ IWN_SCHED_WINSZ);
+ /* Set scheduler frame limit. */
+ iwn_mem_write(sc, sc->sched_base + IWN4965_SCHED_QUEUE_OFFSET(qid) + 4,
+ IWN_SCHED_LIMIT << 16);
+
+ /* Enable interrupts for the queue. */
+ iwn_prph_setbits(sc, IWN4965_SCHED_INTR_MASK, 1 << qid);
+
+ /* Mark the queue as active. */
+ iwn_prph_write(sc, IWN4965_SCHED_QUEUE_STATUS(qid),
+ IWN4965_TXQ_STATUS_ACTIVE | IWN4965_TXQ_STATUS_AGGR_ENA |
+ iwn_tid2fifo[tid] << 1);
+}
+
+static void
+iwn4965_ampdu_tx_stop(struct iwn_softc *sc, uint8_t tid, uint16_t ssn)
+{
+ int qid = 7 + tid;
+
+ /* Stop TX scheduler while we're changing its configuration. */
+ iwn_prph_write(sc, IWN4965_SCHED_QUEUE_STATUS(qid),
+ IWN4965_TXQ_STATUS_CHGACT);
+
+ /* Set starting sequence number from the ADDBA request. */
+ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | (ssn & 0xff));
+ iwn_prph_write(sc, IWN4965_SCHED_QUEUE_RDPTR(qid), ssn);
+
+ /* Disable interrupts for the queue. */
+ iwn_prph_clrbits(sc, IWN4965_SCHED_INTR_MASK, 1 << qid);
+
+ /* Mark the queue as inactive. */
+ iwn_prph_write(sc, IWN4965_SCHED_QUEUE_STATUS(qid),
+ IWN4965_TXQ_STATUS_INACTIVE | iwn_tid2fifo[tid] << 1);
+}
+
+static void
+iwn5000_ampdu_tx_start(struct iwn_softc *sc, struct ieee80211_node *ni,
+ uint8_t tid, uint16_t ssn)
+{
+ struct iwn_node *wn = (void *)ni;
+ int qid = 10 + tid;
+
+ /* Stop TX scheduler while we're changing its configuration. */
+ iwn_prph_write(sc, IWN5000_SCHED_QUEUE_STATUS(qid),
+ IWN5000_TXQ_STATUS_CHGACT);
+
+ /* Assign RA/TID translation to the queue. */
+ iwn_mem_write_2(sc, sc->sched_base + IWN5000_SCHED_TRANS_TBL(qid),
+ wn->id << 4 | tid);
+
+ /* Enable chain-building mode for the queue. */
+ iwn_prph_setbits(sc, IWN5000_SCHED_QCHAIN_SEL, 1 << qid);
+
+ /* Enable aggregation for the queue. */
+ iwn_prph_setbits(sc, IWN5000_SCHED_AGGR_SEL, 1 << qid);
+
+ /* Set starting sequence number from the ADDBA request. */
+ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | (ssn & 0xff));
+ iwn_prph_write(sc, IWN5000_SCHED_QUEUE_RDPTR(qid), ssn);
+
+ /* Set scheduler window size and frame limit. */
+ iwn_mem_write(sc, sc->sched_base + IWN5000_SCHED_QUEUE_OFFSET(qid) + 4,
+ IWN_SCHED_LIMIT << 16 | IWN_SCHED_WINSZ);
+
+ /* Enable interrupts for the queue. */
+ iwn_prph_setbits(sc, IWN5000_SCHED_INTR_MASK, 1 << qid);
+
+ /* Mark the queue as active. */
+ iwn_prph_write(sc, IWN5000_SCHED_QUEUE_STATUS(qid),
+ IWN5000_TXQ_STATUS_ACTIVE | iwn_tid2fifo[tid]);
+}
+
+static void
+iwn5000_ampdu_tx_stop(struct iwn_softc *sc, uint8_t tid, uint16_t ssn)
+{
+ int qid = 10 + tid;
+
+ /* Stop TX scheduler while we're changing its configuration. */
+ iwn_prph_write(sc, IWN5000_SCHED_QUEUE_STATUS(qid),
+ IWN5000_TXQ_STATUS_CHGACT);
+
+ /* Disable aggregation for the queue. */
+ iwn_prph_clrbits(sc, IWN5000_SCHED_AGGR_SEL, 1 << qid);
+
+ /* Set starting sequence number from the ADDBA request. */
+ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | (ssn & 0xff));
+ iwn_prph_write(sc, IWN5000_SCHED_QUEUE_RDPTR(qid), ssn);
+
+ /* Disable interrupts for the queue. */
+ iwn_prph_clrbits(sc, IWN5000_SCHED_INTR_MASK, 1 << qid);
+
+ /* Mark the queue as inactive. */
+ iwn_prph_write(sc, IWN5000_SCHED_QUEUE_STATUS(qid),
+ IWN5000_TXQ_STATUS_INACTIVE | iwn_tid2fifo[tid]);
+}
+#endif /* !IEEE80211_NO_HT */
+
+/*
+ * Query calibration tables from the initialization firmware. We do this
+ * only once at first boot. Called from a process context.
+ */
+static int
+iwn5000_query_calibration(struct iwn_softc *sc)
+{
+ struct iwn5000_calib_config cmd;
+ int error;
+ clock_t clk;
+
+ ASSERT(mutex_owned(&sc->sc_mtx));
+
+ memset(&cmd, 0, sizeof cmd);
+ cmd.ucode.once.enable = 0xffffffff;
+ cmd.ucode.once.start = 0xffffffff;
+ cmd.ucode.once.send = 0xffffffff;
+ cmd.ucode.flags = 0xffffffff;
+ error = iwn_cmd(sc, IWN5000_CMD_CALIB_CONFIG, &cmd, sizeof cmd, 0);
+ if (error != 0)
+ return error;
+
+ /* Wait at most two seconds for calibration to complete. */
+ clk = ddi_get_lbolt() + drv_usectohz(2000000);
+ while (!(sc->sc_flags & IWN_FLAG_CALIB_DONE))
+ if (cv_timedwait(&sc->sc_calib_cv, &sc->sc_mtx, clk) < 0)
+ return (IWN_FAIL);
+
+ return (IWN_SUCCESS);
+}
+
+/*
+ * Send calibration results to the runtime firmware. These results were
+ * obtained on first boot from the initialization firmware.
+ */
+static int
+iwn5000_send_calibration(struct iwn_softc *sc)
+{
+ int idx, error;
+
+ for (idx = 0; idx < 5; idx++) {
+ if (sc->calibcmd[idx].buf == NULL)
+ continue; /* No results available. */
+ error = iwn_cmd(sc, IWN_CMD_PHY_CALIB, sc->calibcmd[idx].buf,
+ sc->calibcmd[idx].len, 0);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not send calibration result");
+ return error;
+ }
+ }
+ return 0;
+}
+
+static int
+iwn5000_send_wimax_coex(struct iwn_softc *sc)
+{
+ struct iwn5000_wimax_coex wimax;
+
+#ifdef notyet
+ if (sc->hw_type == IWN_HW_REV_TYPE_6050) {
+ /* Enable WiMAX coexistence for combo adapters. */
+ wimax.flags =
+ IWN_WIMAX_COEX_ASSOC_WA_UNMASK |
+ IWN_WIMAX_COEX_UNASSOC_WA_UNMASK |
+ IWN_WIMAX_COEX_STA_TABLE_VALID |
+ IWN_WIMAX_COEX_ENABLE;
+ memcpy(wimax.events, iwn6050_wimax_events,
+ sizeof iwn6050_wimax_events);
+ } else
+#endif
+ {
+ /* Disable WiMAX coexistence. */
+ wimax.flags = 0;
+ memset(wimax.events, 0, sizeof wimax.events);
+ }
+ return iwn_cmd(sc, IWN5000_CMD_WIMAX_COEX, &wimax, sizeof wimax, 0);
+}
+
+static int
+iwn6000_temp_offset_calib(struct iwn_softc *sc)
+{
+ struct iwn6000_phy_calib_temp_offset cmd;
+
+ memset(&cmd, 0, sizeof cmd);
+ cmd.code = IWN6000_PHY_CALIB_TEMP_OFFSET;
+ cmd.ngroups = 1;
+ cmd.isvalid = 1;
+ if (sc->eeprom_temp != 0)
+ cmd.offset = htole16(sc->eeprom_temp);
+ else
+ cmd.offset = htole16(IWN_DEFAULT_TEMP_OFFSET);
+ sc->sc_toff.t6000->toff.value.l = le16toh(cmd.offset);
+ return iwn_cmd(sc, IWN_CMD_PHY_CALIB, &cmd, sizeof cmd, 0);
+}
+
+static int
+iwn2000_temp_offset_calib(struct iwn_softc *sc)
+{
+ struct iwn2000_phy_calib_temp_offset cmd;
+
+ memset(&cmd, 0, sizeof cmd);
+ cmd.code = IWN2000_PHY_CALIB_TEMP_OFFSET;
+ cmd.ngroups = 1;
+ cmd.isvalid = 1;
+ if (sc->eeprom_rawtemp != 0) {
+ cmd.offset_low = htole16(sc->eeprom_rawtemp);
+ cmd.offset_high = htole16(sc->eeprom_temp);
+ } else {
+ cmd.offset_low = htole16(IWN_DEFAULT_TEMP_OFFSET);
+ cmd.offset_high = htole16(IWN_DEFAULT_TEMP_OFFSET);
+ }
+ cmd.burnt_voltage_ref = htole16(sc->eeprom_voltage);
+ sc->sc_toff.t2000->toff_lo.value.l = le16toh(cmd.offset_low);
+ sc->sc_toff.t2000->toff_hi.value.l = le16toh(cmd.offset_high);
+ sc->sc_toff.t2000->volt.value.l = le16toh(cmd.burnt_voltage_ref);
+
+ return iwn_cmd(sc, IWN_CMD_PHY_CALIB, &cmd, sizeof cmd, 0);
+}
+
+/*
+ * This function is called after the runtime firmware notifies us of its
+ * readiness (called in a process context).
+ */
+static int
+iwn4965_post_alive(struct iwn_softc *sc)
+{
+ int error, qid;
+
+ if ((error = iwn_nic_lock(sc)) != 0)
+ return error;
+
+ /* Clear TX scheduler state in SRAM. */
+ sc->sched_base = iwn_prph_read(sc, IWN_SCHED_SRAM_ADDR);
+ iwn_mem_set_region_4(sc, sc->sched_base + IWN4965_SCHED_CTX_OFF, 0,
+ IWN4965_SCHED_CTX_LEN / sizeof (uint32_t));
+
+ /* Set physical address of TX scheduler rings (1KB aligned). */
+ iwn_prph_write(sc, IWN4965_SCHED_DRAM_ADDR, sc->sched_dma.paddr >> 10);
+
+ IWN_SETBITS(sc, IWN_FH_TX_CHICKEN, IWN_FH_TX_CHICKEN_SCHED_RETRY);
+
+ /* Disable chain mode for all our 16 queues. */
+ iwn_prph_write(sc, IWN4965_SCHED_QCHAIN_SEL, 0);
+
+ for (qid = 0; qid < IWN4965_NTXQUEUES; qid++) {
+ iwn_prph_write(sc, IWN4965_SCHED_QUEUE_RDPTR(qid), 0);
+ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | 0);
+
+ /* Set scheduler window size. */
+ iwn_mem_write(sc, sc->sched_base +
+ IWN4965_SCHED_QUEUE_OFFSET(qid), IWN_SCHED_WINSZ);
+ /* Set scheduler frame limit. */
+ iwn_mem_write(sc, sc->sched_base +
+ IWN4965_SCHED_QUEUE_OFFSET(qid) + 4,
+ IWN_SCHED_LIMIT << 16);
+ }
+
+ /* Enable interrupts for all our 16 queues. */
+ iwn_prph_write(sc, IWN4965_SCHED_INTR_MASK, 0xffff);
+ /* Identify TX FIFO rings (0-7). */
+ iwn_prph_write(sc, IWN4965_SCHED_TXFACT, 0xff);
+
+ /* Mark TX rings (4 EDCA + cmd + 2 HCCA) as active. */
+ for (qid = 0; qid < 7; qid++) {
+ static uint8_t qid2fifo[] = { 3, 2, 1, 0, 4, 5, 6 };
+ iwn_prph_write(sc, IWN4965_SCHED_QUEUE_STATUS(qid),
+ IWN4965_TXQ_STATUS_ACTIVE | qid2fifo[qid] << 1);
+ }
+ iwn_nic_unlock(sc);
+ return 0;
+}
+
+/*
+ * This function is called after the initialization or runtime firmware
+ * notifies us of its readiness (called in a process context).
+ */
+static int
+iwn5000_post_alive(struct iwn_softc *sc)
+{
+ int error, qid;
+
+ /* Switch to using ICT interrupt mode. */
+ iwn5000_ict_reset(sc);
+
+ if ((error = iwn_nic_lock(sc)) != 0)
+ return error;
+
+ /* Clear TX scheduler state in SRAM. */
+ sc->sched_base = iwn_prph_read(sc, IWN_SCHED_SRAM_ADDR);
+ iwn_mem_set_region_4(sc, sc->sched_base + IWN5000_SCHED_CTX_OFF, 0,
+ IWN5000_SCHED_CTX_LEN / sizeof (uint32_t));
+
+ /* Set physical address of TX scheduler rings (1KB aligned). */
+ iwn_prph_write(sc, IWN5000_SCHED_DRAM_ADDR, sc->sched_dma.paddr >> 10);
+
+ IWN_SETBITS(sc, IWN_FH_TX_CHICKEN, IWN_FH_TX_CHICKEN_SCHED_RETRY);
+
+ /* Enable chain mode for all queues, except command queue. */
+ iwn_prph_write(sc, IWN5000_SCHED_QCHAIN_SEL, 0xfffef);
+ iwn_prph_write(sc, IWN5000_SCHED_AGGR_SEL, 0);
+
+ for (qid = 0; qid < IWN5000_NTXQUEUES; qid++) {
+ iwn_prph_write(sc, IWN5000_SCHED_QUEUE_RDPTR(qid), 0);
+ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | 0);
+
+ iwn_mem_write(sc, sc->sched_base +
+ IWN5000_SCHED_QUEUE_OFFSET(qid), 0);
+ /* Set scheduler window size and frame limit. */
+ iwn_mem_write(sc, sc->sched_base +
+ IWN5000_SCHED_QUEUE_OFFSET(qid) + 4,
+ IWN_SCHED_LIMIT << 16 | IWN_SCHED_WINSZ);
+ }
+
+ /* Enable interrupts for all our 20 queues. */
+ iwn_prph_write(sc, IWN5000_SCHED_INTR_MASK, 0xfffff);
+ /* Identify TX FIFO rings (0-7). */
+ iwn_prph_write(sc, IWN5000_SCHED_TXFACT, 0xff);
+
+ /* Mark TX rings (4 EDCA + cmd + 2 HCCA) as active. */
+ for (qid = 0; qid < 7; qid++) {
+ static uint8_t qid2fifo[] = { 3, 2, 1, 0, 7, 5, 6 };
+ iwn_prph_write(sc, IWN5000_SCHED_QUEUE_STATUS(qid),
+ IWN5000_TXQ_STATUS_ACTIVE | qid2fifo[qid]);
+ }
+ iwn_nic_unlock(sc);
+
+ /* Configure WiMAX coexistence for combo adapters. */
+ error = iwn5000_send_wimax_coex(sc);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not configure WiMAX coexistence");
+ return error;
+ }
+ if (sc->hw_type != IWN_HW_REV_TYPE_5150) {
+ struct iwn5000_phy_calib_crystal cmd;
+
+ /* Perform crystal calibration. */
+ memset(&cmd, 0, sizeof cmd);
+ cmd.code = IWN5000_PHY_CALIB_CRYSTAL;
+ cmd.ngroups = 1;
+ cmd.isvalid = 1;
+ cmd.cap_pin[0] = le32toh(sc->eeprom_crystal) & 0xff;
+ cmd.cap_pin[1] = (le32toh(sc->eeprom_crystal) >> 16) & 0xff;
+ error = iwn_cmd(sc, IWN_CMD_PHY_CALIB, &cmd, sizeof cmd, 0);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!crystal calibration failed");
+ return error;
+ }
+ }
+ if (!(sc->sc_flags & IWN_FLAG_CALIB_DONE)) {
+ /* Query calibration from the initialization firmware. */
+ if ((error = iwn5000_query_calibration(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not query calibration");
+ return error;
+ }
+ /*
+ * We have the calibration results now, reboot with the
+ * runtime firmware (call ourselves recursively!)
+ */
+ iwn_hw_stop(sc, B_FALSE);
+ error = iwn_hw_init(sc);
+ } else {
+ /* Send calibration results to runtime firmware. */
+ error = iwn5000_send_calibration(sc);
+ }
+ return error;
+}
+
+/*
+ * The firmware boot code is small and is intended to be copied directy into
+ * the NIC internal memory (no DMA transfer).
+ */
+static int
+iwn4965_load_bootcode(struct iwn_softc *sc, const uint8_t *ucode, int size)
+{
+ int error, ntries;
+
+ size /= sizeof (uint32_t);
+
+ if ((error = iwn_nic_lock(sc)) != 0)
+ return error;
+
+ /* Copy microcode image into NIC memory. */
+ iwn_prph_write_region_4(sc, IWN_BSM_SRAM_BASE,
+ /*LINTED: E_PTR_BAD_CAST_ALIGN*/
+ (const uint32_t *)ucode, size);
+
+ iwn_prph_write(sc, IWN_BSM_WR_MEM_SRC, 0);
+ iwn_prph_write(sc, IWN_BSM_WR_MEM_DST, IWN_FW_TEXT_BASE);
+ iwn_prph_write(sc, IWN_BSM_WR_DWCOUNT, size);
+
+ /* Start boot load now. */
+ iwn_prph_write(sc, IWN_BSM_WR_CTRL, IWN_BSM_WR_CTRL_START);
+
+ /* Wait for transfer to complete. */
+ for (ntries = 0; ntries < 1000; ntries++) {
+ if (!(iwn_prph_read(sc, IWN_BSM_WR_CTRL) &
+ IWN_BSM_WR_CTRL_START))
+ break;
+ DELAY(10);
+ }
+ if (ntries == 1000) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not load boot firmware");
+ iwn_nic_unlock(sc);
+ return ETIMEDOUT;
+ }
+
+ /* Enable boot after power up. */
+ iwn_prph_write(sc, IWN_BSM_WR_CTRL, IWN_BSM_WR_CTRL_START_EN);
+
+ iwn_nic_unlock(sc);
+ return 0;
+}
+
+static int
+iwn4965_load_firmware(struct iwn_softc *sc)
+{
+ struct iwn_fw_info *fw = &sc->fw;
+ struct iwn_dma_info *dma = &sc->fw_dma;
+ int error;
+ clock_t clk;
+
+ ASSERT(mutex_owned(&sc->sc_mtx));
+
+ /* Copy initialization sections into pre-allocated DMA-safe memory. */
+ memcpy(dma->vaddr, fw->init.data, fw->init.datasz);
+ memcpy((char *)dma->vaddr + IWN4965_FW_DATA_MAXSZ,
+ fw->init.text, fw->init.textsz);
+ (void) ddi_dma_sync(dma->dma_hdl, 0, 0, DDI_DMA_SYNC_FORDEV);
+
+ /* Tell adapter where to find initialization sections. */
+ if ((error = iwn_nic_lock(sc)) != 0)
+ return error;
+ iwn_prph_write(sc, IWN_BSM_DRAM_DATA_ADDR, dma->paddr >> 4);
+ iwn_prph_write(sc, IWN_BSM_DRAM_DATA_SIZE, fw->init.datasz);
+ iwn_prph_write(sc, IWN_BSM_DRAM_TEXT_ADDR,
+ (dma->paddr + IWN4965_FW_DATA_MAXSZ) >> 4);
+ iwn_prph_write(sc, IWN_BSM_DRAM_TEXT_SIZE, fw->init.textsz);
+ iwn_nic_unlock(sc);
+
+ /* Load firmware boot code. */
+ error = iwn4965_load_bootcode(sc, fw->boot.text, fw->boot.textsz);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not load boot firmware");
+ return error;
+ }
+ /* Now press "execute". */
+ IWN_WRITE(sc, IWN_RESET, 0);
+
+ /* Wait at most one second for first alive notification. */
+ clk = ddi_get_lbolt() + drv_usectohz(1000000);
+ while ((sc->sc_flags & IWN_FLAG_FW_ALIVE) == 0) {
+ if (cv_timedwait(&sc->sc_alive_cv, &sc->sc_mtx, clk) < 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!timeout waiting for adapter to initialize");
+ return (IWN_FAIL);
+ }
+ }
+
+ /* Retrieve current temperature for initial TX power calibration. */
+ sc->rawtemp = sc->ucode_info.temp[3].chan20MHz;
+ sc->temp = iwn4965_get_temperature(sc);
+ sc->sc_misc->temp.value.ul = sc->temp;
+
+ /* Copy runtime sections into pre-allocated DMA-safe memory. */
+ memcpy(dma->vaddr, fw->main.data, fw->main.datasz);
+ memcpy((char *)dma->vaddr + IWN4965_FW_DATA_MAXSZ,
+ fw->main.text, fw->main.textsz);
+ (void) ddi_dma_sync(dma->dma_hdl, 0, 0, DDI_DMA_SYNC_FORDEV);
+
+ /* Tell adapter where to find runtime sections. */
+ if ((error = iwn_nic_lock(sc)) != 0)
+ return error;
+ iwn_prph_write(sc, IWN_BSM_DRAM_DATA_ADDR, dma->paddr >> 4);
+ iwn_prph_write(sc, IWN_BSM_DRAM_DATA_SIZE, fw->main.datasz);
+ iwn_prph_write(sc, IWN_BSM_DRAM_TEXT_ADDR,
+ (dma->paddr + IWN4965_FW_DATA_MAXSZ) >> 4);
+ iwn_prph_write(sc, IWN_BSM_DRAM_TEXT_SIZE,
+ IWN_FW_UPDATED | fw->main.textsz);
+ iwn_nic_unlock(sc);
+
+ return 0;
+}
+
+static int
+iwn5000_load_firmware_section(struct iwn_softc *sc, uint32_t dst,
+ const uint8_t *section, int size)
+{
+ struct iwn_dma_info *dma = &sc->fw_dma;
+ int error;
+ clock_t clk;
+
+ ASSERT(mutex_owned(&sc->sc_mtx));
+
+ /* Copy firmware section into pre-allocated DMA-safe memory. */
+ memcpy(dma->vaddr, section, size);
+ (void) ddi_dma_sync(dma->dma_hdl, 0, 0, DDI_DMA_SYNC_FORDEV);
+
+ if ((error = iwn_nic_lock(sc)) != 0)
+ return error;
+
+ IWN_WRITE(sc, IWN_FH_TX_CONFIG(IWN_SRVC_DMACHNL),
+ IWN_FH_TX_CONFIG_DMA_PAUSE);
+
+ IWN_WRITE(sc, IWN_FH_SRAM_ADDR(IWN_SRVC_DMACHNL), dst);
+ IWN_WRITE(sc, IWN_FH_TFBD_CTRL0(IWN_SRVC_DMACHNL),
+ IWN_LOADDR(dma->paddr));
+ IWN_WRITE(sc, IWN_FH_TFBD_CTRL1(IWN_SRVC_DMACHNL),
+ IWN_HIADDR(dma->paddr) << 28 | size);
+ IWN_WRITE(sc, IWN_FH_TXBUF_STATUS(IWN_SRVC_DMACHNL),
+ IWN_FH_TXBUF_STATUS_TBNUM(1) |
+ IWN_FH_TXBUF_STATUS_TBIDX(1) |
+ IWN_FH_TXBUF_STATUS_TFBD_VALID);
+
+ /* Kick Flow Handler to start DMA transfer. */
+ IWN_WRITE(sc, IWN_FH_TX_CONFIG(IWN_SRVC_DMACHNL),
+ IWN_FH_TX_CONFIG_DMA_ENA | IWN_FH_TX_CONFIG_CIRQ_HOST_ENDTFD);
+
+ iwn_nic_unlock(sc);
+
+ /* Wait at most five seconds for FH DMA transfer to complete. */
+ clk = ddi_get_lbolt() + drv_usectohz(5000000);
+ while ((sc->sc_flags & IWN_FLAG_FW_DMA) == 0) {
+ if (cv_timedwait(&sc->sc_fhdma_cv, &sc->sc_mtx, clk) < 0)
+ return (IWN_FAIL);
+ }
+ sc->sc_flags &= ~IWN_FLAG_FW_DMA;
+
+ return (IWN_SUCCESS);
+}
+
+static int
+iwn5000_load_firmware(struct iwn_softc *sc)
+{
+ struct iwn_fw_part *fw;
+ int error;
+
+ /* Load the initialization firmware on first boot only. */
+ fw = (sc->sc_flags & IWN_FLAG_CALIB_DONE) ?
+ &sc->fw.main : &sc->fw.init;
+
+ error = iwn5000_load_firmware_section(sc, IWN_FW_TEXT_BASE,
+ fw->text, fw->textsz);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not load firmware %s section", ".text");
+ return error;
+ }
+ error = iwn5000_load_firmware_section(sc, IWN_FW_DATA_BASE,
+ fw->data, fw->datasz);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not load firmware %s section", ".data");
+ return error;
+ }
+
+ /* Now press "execute". */
+ IWN_WRITE(sc, IWN_RESET, 0);
+ return 0;
+}
+
+/*
+ * Extract text and data sections from a legacy firmware image.
+ */
+static int
+iwn_read_firmware_leg(struct iwn_softc *sc, struct iwn_fw_info *fw)
+{
+ _NOTE(ARGUNUSED(sc));
+ const uint32_t *ptr;
+ size_t hdrlen = 24;
+ uint32_t rev;
+
+ /*LINTED: E_PTR_BAD_CAST_ALIGN*/
+ ptr = (const uint32_t *)fw->data;
+ rev = le32toh(*ptr++);
+
+ /* Check firmware API version. */
+ if (IWN_FW_API(rev) <= 1) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!bad firmware, need API version >=2");
+ return EINVAL;
+ }
+ if (IWN_FW_API(rev) >= 3) {
+ /* Skip build number (version 2 header). */
+ hdrlen += 4;
+ ptr++;
+ }
+ if (fw->size < hdrlen) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!firmware too short: %lld bytes", (longlong_t)fw->size);
+ return EINVAL;
+ }
+ fw->main.textsz = le32toh(*ptr++);
+ fw->main.datasz = le32toh(*ptr++);
+ fw->init.textsz = le32toh(*ptr++);
+ fw->init.datasz = le32toh(*ptr++);
+ fw->boot.textsz = le32toh(*ptr++);
+
+ /* Check that all firmware sections fit. */
+ if (fw->size < hdrlen + fw->main.textsz + fw->main.datasz +
+ fw->init.textsz + fw->init.datasz + fw->boot.textsz) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!firmware too short: %lld bytes", (longlong_t)fw->size);
+ return EINVAL;
+ }
+
+ /* Get pointers to firmware sections. */
+ fw->main.text = (const uint8_t *)ptr;
+ fw->main.data = fw->main.text + fw->main.textsz;
+ fw->init.text = fw->main.data + fw->main.datasz;
+ fw->init.data = fw->init.text + fw->init.textsz;
+ fw->boot.text = fw->init.data + fw->init.datasz;
+ return 0;
+}
+
+/*
+ * Extract text and data sections from a TLV firmware image.
+ */
+static int
+iwn_read_firmware_tlv(struct iwn_softc *sc, struct iwn_fw_info *fw,
+ uint16_t alt)
+{
+ _NOTE(ARGUNUSED(sc));
+ const struct iwn_fw_tlv_hdr *hdr;
+ const struct iwn_fw_tlv *tlv;
+ const uint8_t *ptr, *end;
+ uint64_t altmask;
+ uint32_t len;
+
+ if (fw->size < sizeof (*hdr)) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!firmware too short: %lld bytes", (longlong_t)fw->size);
+ return EINVAL;
+ }
+ hdr = (const struct iwn_fw_tlv_hdr *)fw->data;
+ if (hdr->signature != htole32(IWN_FW_SIGNATURE)) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!bad firmware signature 0x%08x", le32toh(hdr->signature));
+ return EINVAL;
+ }
+
+ /*
+ * Select the closest supported alternative that is less than
+ * or equal to the specified one.
+ */
+ altmask = le64toh(hdr->altmask);
+ while (alt > 0 && !(altmask & (1ULL << alt)))
+ alt--; /* Downgrade. */
+ IWN_DBG("using alternative %d", alt);
+
+ ptr = (const uint8_t *)(hdr + 1);
+ end = (const uint8_t *)(fw->data + fw->size);
+
+ /* Parse type-length-value fields. */
+ while (ptr + sizeof (*tlv) <= end) {
+ tlv = (const struct iwn_fw_tlv *)ptr;
+ len = le32toh(tlv->len);
+
+ ptr += sizeof (*tlv);
+ if (ptr + len > end) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!firmware too short: %lld bytes",
+ (longlong_t)fw->size);
+ return EINVAL;
+ }
+ /* Skip other alternatives. */
+ if (tlv->alt != 0 && le16toh(tlv->alt) != alt) {
+ IWN_DBG("skipping other alternative");
+ goto next;
+ }
+
+ switch (le16toh(tlv->type)) {
+ case IWN_FW_TLV_MAIN_TEXT:
+ fw->main.text = ptr;
+ fw->main.textsz = len;
+ break;
+ case IWN_FW_TLV_MAIN_DATA:
+ fw->main.data = ptr;
+ fw->main.datasz = len;
+ break;
+ case IWN_FW_TLV_INIT_TEXT:
+ fw->init.text = ptr;
+ fw->init.textsz = len;
+ break;
+ case IWN_FW_TLV_INIT_DATA:
+ fw->init.data = ptr;
+ fw->init.datasz = len;
+ break;
+ case IWN_FW_TLV_BOOT_TEXT:
+ fw->boot.text = ptr;
+ fw->boot.textsz = len;
+ break;
+ case IWN_FW_TLV_ENH_SENS:
+ if (len != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!TLV type %d has invalid size %u",
+ le16toh(tlv->type), len);
+ goto next;
+ }
+ sc->sc_flags |= IWN_FLAG_ENH_SENS;
+ break;
+ case IWN_FW_TLV_PHY_CALIB:
+ if (len != sizeof(uint32_t)) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!TLV type %d has invalid size %u",
+ le16toh(tlv->type), len);
+ goto next;
+ }
+ if (le32toh(*ptr) <= IWN5000_PHY_CALIB_MAX) {
+ sc->reset_noise_gain = le32toh(*ptr);
+ sc->noise_gain = le32toh(*ptr) + 1;
+ }
+ break;
+ case IWN_FW_TLV_FLAGS:
+ if (len < sizeof(uint32_t))
+ break;
+ if (len % sizeof(uint32_t))
+ break;
+ sc->tlv_feature_flags = le32toh(*ptr);
+ IWN_DBG("feature: 0x%08x", sc->tlv_feature_flags);
+ break;
+ default:
+ IWN_DBG("TLV type %d not handled", le16toh(tlv->type));
+ break;
+ }
+ next: /* TLV fields are 32-bit aligned. */
+ ptr += (len + 3) & ~3;
+ }
+ return 0;
+}
+
+static int
+iwn_read_firmware(struct iwn_softc *sc)
+{
+ struct iwn_fw_info *fw = &sc->fw;
+ firmware_handle_t fwh;
+ int error;
+
+ /*
+ * Some PHY calibration commands are firmware-dependent; these
+ * are the default values that will be overridden if
+ * necessary.
+ */
+ sc->reset_noise_gain = IWN5000_PHY_CALIB_RESET_NOISE_GAIN;
+ sc->noise_gain = IWN5000_PHY_CALIB_NOISE_GAIN;
+
+ /* Initialize for error returns */
+ fw->data = NULL;
+ fw->size = 0;
+
+ /* Open firmware image. */
+ if ((error = firmware_open("iwn", sc->fwname, &fwh)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not get firmware handle %s", sc->fwname);
+ return error;
+ }
+ fw->size = firmware_get_size(fwh);
+ if (fw->size < sizeof (uint32_t)) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!firmware too short: %lld bytes", (longlong_t)fw->size);
+ (void) firmware_close(fwh);
+ return EINVAL;
+ }
+
+ /* Read the firmware. */
+ fw->data = kmem_alloc(fw->size, KM_SLEEP);
+ error = firmware_read(fwh, 0, fw->data, fw->size);
+ (void) firmware_close(fwh);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not read firmware %s", sc->fwname);
+ goto out;
+ }
+
+ /* Retrieve text and data sections. */
+ /*LINTED: E_PTR_BAD_CAST_ALIGN*/
+ if (*(const uint32_t *)fw->data != 0) /* Legacy image. */
+ error = iwn_read_firmware_leg(sc, fw);
+ else
+ error = iwn_read_firmware_tlv(sc, fw, 1);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not read firmware sections");
+ goto out;
+ }
+
+ /* Make sure text and data sections fit in hardware memory. */
+ if (fw->main.textsz > sc->fw_text_maxsz ||
+ fw->main.datasz > sc->fw_data_maxsz ||
+ fw->init.textsz > sc->fw_text_maxsz ||
+ fw->init.datasz > sc->fw_data_maxsz ||
+ fw->boot.textsz > IWN_FW_BOOT_TEXT_MAXSZ ||
+ (fw->boot.textsz & 3) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!firmware sections too large");
+ goto out;
+ }
+
+ /* We can proceed with loading the firmware. */
+ return 0;
+out:
+ kmem_free(fw->data, fw->size);
+ fw->data = NULL;
+ fw->size = 0;
+ return error ? error : EINVAL;
+}
+
+static int
+iwn_clock_wait(struct iwn_softc *sc)
+{
+ int ntries;
+
+ /* Set "initialization complete" bit. */
+ IWN_SETBITS(sc, IWN_GP_CNTRL, IWN_GP_CNTRL_INIT_DONE);
+
+ /* Wait for clock stabilization. */
+ for (ntries = 0; ntries < 2500; ntries++) {
+ if (IWN_READ(sc, IWN_GP_CNTRL) & IWN_GP_CNTRL_MAC_CLOCK_READY)
+ return 0;
+ DELAY(10);
+ }
+ dev_err(sc->sc_dip, CE_WARN,
+ "!timeout waiting for clock stabilization");
+ return ETIMEDOUT;
+}
+
+static int
+iwn_apm_init(struct iwn_softc *sc)
+{
+ uint32_t reg;
+ int error;
+
+ /* Disable L0s exit timer (NMI bug workaround). */
+ IWN_SETBITS(sc, IWN_GIO_CHICKEN, IWN_GIO_CHICKEN_DIS_L0S_TIMER);
+ /* Don't wait for ICH L0s (ICH bug workaround). */
+ IWN_SETBITS(sc, IWN_GIO_CHICKEN, IWN_GIO_CHICKEN_L1A_NO_L0S_RX);
+
+ /* Set FH wait threshold to max (HW bug under stress workaround). */
+ IWN_SETBITS(sc, IWN_DBG_HPET_MEM, 0xffff0000);
+
+ /* Enable HAP INTA to move adapter from L1a to L0s. */
+ IWN_SETBITS(sc, IWN_HW_IF_CONFIG, IWN_HW_IF_CONFIG_HAP_WAKE_L1A);
+
+ /* Retrieve PCIe Active State Power Management (ASPM). */
+ reg = pci_config_get32(sc->sc_pcih,
+ sc->sc_cap_off + PCIE_LINKCTL);
+ /* Workaround for HW instability in PCIe L0->L0s->L1 transition. */
+ if (reg & PCIE_LINKCTL_ASPM_CTL_L1) /* L1 Entry enabled. */
+ IWN_SETBITS(sc, IWN_GIO, IWN_GIO_L0S_ENA);
+ else
+ IWN_CLRBITS(sc, IWN_GIO, IWN_GIO_L0S_ENA);
+
+ if (sc->hw_type != IWN_HW_REV_TYPE_4965 &&
+ sc->hw_type <= IWN_HW_REV_TYPE_1000)
+ IWN_SETBITS(sc, IWN_ANA_PLL, IWN_ANA_PLL_INIT);
+
+ /* Wait for clock stabilization before accessing prph. */
+ if ((error = iwn_clock_wait(sc)) != 0)
+ return error;
+
+ if ((error = iwn_nic_lock(sc)) != 0)
+ return error;
+ if (sc->hw_type == IWN_HW_REV_TYPE_4965) {
+ /* Enable DMA and BSM (Bootstrap State Machine). */
+ iwn_prph_write(sc, IWN_APMG_CLK_EN,
+ IWN_APMG_CLK_CTRL_DMA_CLK_RQT |
+ IWN_APMG_CLK_CTRL_BSM_CLK_RQT);
+ } else {
+ /* Enable DMA. */
+ iwn_prph_write(sc, IWN_APMG_CLK_EN,
+ IWN_APMG_CLK_CTRL_DMA_CLK_RQT);
+ }
+ DELAY(20);
+ /* Disable L1-Active. */
+ iwn_prph_setbits(sc, IWN_APMG_PCI_STT, IWN_APMG_PCI_STT_L1A_DIS);
+ iwn_nic_unlock(sc);
+
+ return 0;
+}
+
+static void
+iwn_apm_stop_master(struct iwn_softc *sc)
+{
+ int ntries;
+
+ /* Stop busmaster DMA activity. */
+ IWN_SETBITS(sc, IWN_RESET, IWN_RESET_STOP_MASTER);
+ for (ntries = 0; ntries < 100; ntries++) {
+ if (IWN_READ(sc, IWN_RESET) & IWN_RESET_MASTER_DISABLED)
+ return;
+ DELAY(10);
+ }
+ dev_err(sc->sc_dip, CE_WARN,
+ "!timeout waiting for master");
+}
+
+static void
+iwn_apm_stop(struct iwn_softc *sc)
+{
+ iwn_apm_stop_master(sc);
+
+ /* Reset the entire device. */
+ IWN_SETBITS(sc, IWN_RESET, IWN_RESET_SW);
+ DELAY(10);
+ /* Clear "initialization complete" bit. */
+ IWN_CLRBITS(sc, IWN_GP_CNTRL, IWN_GP_CNTRL_INIT_DONE);
+}
+
+static int
+iwn4965_nic_config(struct iwn_softc *sc)
+{
+ if (IWN_RFCFG_TYPE(sc->rfcfg) == 1) {
+ /*
+ * I don't believe this to be correct but this is what the
+ * vendor driver is doing. Probably the bits should not be
+ * shifted in IWN_RFCFG_*.
+ */
+ IWN_SETBITS(sc, IWN_HW_IF_CONFIG,
+ IWN_RFCFG_TYPE(sc->rfcfg) |
+ IWN_RFCFG_STEP(sc->rfcfg) |
+ IWN_RFCFG_DASH(sc->rfcfg));
+ }
+ IWN_SETBITS(sc, IWN_HW_IF_CONFIG,
+ IWN_HW_IF_CONFIG_RADIO_SI | IWN_HW_IF_CONFIG_MAC_SI);
+ return 0;
+}
+
+static int
+iwn5000_nic_config(struct iwn_softc *sc)
+{
+ uint32_t tmp;
+ int error;
+
+ if (IWN_RFCFG_TYPE(sc->rfcfg) < 3) {
+ IWN_SETBITS(sc, IWN_HW_IF_CONFIG,
+ IWN_RFCFG_TYPE(sc->rfcfg) |
+ IWN_RFCFG_STEP(sc->rfcfg) |
+ IWN_RFCFG_DASH(sc->rfcfg));
+ }
+ IWN_SETBITS(sc, IWN_HW_IF_CONFIG,
+ IWN_HW_IF_CONFIG_RADIO_SI | IWN_HW_IF_CONFIG_MAC_SI);
+
+ if ((error = iwn_nic_lock(sc)) != 0)
+ return error;
+ iwn_prph_setbits(sc, IWN_APMG_PS, IWN_APMG_PS_EARLY_PWROFF_DIS);
+
+ if (sc->hw_type == IWN_HW_REV_TYPE_1000) {
+ /*
+ * Select first Switching Voltage Regulator (1.32V) to
+ * solve a stability issue related to noisy DC2DC line
+ * in the silicon of 1000 Series.
+ */
+ tmp = iwn_prph_read(sc, IWN_APMG_DIGITAL_SVR);
+ tmp &= ~IWN_APMG_DIGITAL_SVR_VOLTAGE_MASK;
+ tmp |= IWN_APMG_DIGITAL_SVR_VOLTAGE_1_32;
+ iwn_prph_write(sc, IWN_APMG_DIGITAL_SVR, tmp);
+ }
+ iwn_nic_unlock(sc);
+
+ if (sc->sc_flags & IWN_FLAG_INTERNAL_PA) {
+ /* Use internal power amplifier only. */
+ IWN_WRITE(sc, IWN_GP_DRIVER, IWN_GP_DRIVER_RADIO_2X2_IPA);
+ }
+ if ((sc->hw_type == IWN_HW_REV_TYPE_6050 ||
+ sc->hw_type == IWN_HW_REV_TYPE_6005) && sc->calib_ver >= 6) {
+ /* Indicate that ROM calibration version is >=6. */
+ IWN_SETBITS(sc, IWN_GP_DRIVER, IWN_GP_DRIVER_CALIB_VER6);
+ }
+ if (sc->hw_type == IWN_HW_REV_TYPE_6005)
+ IWN_SETBITS(sc, IWN_GP_DRIVER, IWN_GP_DRIVER_6050_1X2);
+ if (sc->hw_type == IWN_HW_REV_TYPE_2030 ||
+ sc->hw_type == IWN_HW_REV_TYPE_2000 ||
+ sc->hw_type == IWN_HW_REV_TYPE_135 ||
+ sc->hw_type == IWN_HW_REV_TYPE_105)
+ IWN_SETBITS(sc, IWN_GP_DRIVER, IWN_GP_DRIVER_RADIO_IQ_INVERT);
+ return 0;
+}
+
+/*
+ * Take NIC ownership over Intel Active Management Technology (AMT).
+ */
+static int
+iwn_hw_prepare(struct iwn_softc *sc)
+{
+ int ntries;
+
+ /* Check if hardware is ready. */
+ IWN_SETBITS(sc, IWN_HW_IF_CONFIG, IWN_HW_IF_CONFIG_NIC_READY);
+ for (ntries = 0; ntries < 5; ntries++) {
+ if (IWN_READ(sc, IWN_HW_IF_CONFIG) &
+ IWN_HW_IF_CONFIG_NIC_READY)
+ return 0;
+ DELAY(10);
+ }
+
+ /* Hardware not ready, force into ready state. */
+ IWN_SETBITS(sc, IWN_HW_IF_CONFIG, IWN_HW_IF_CONFIG_PREPARE);
+ for (ntries = 0; ntries < 15000; ntries++) {
+ if (!(IWN_READ(sc, IWN_HW_IF_CONFIG) &
+ IWN_HW_IF_CONFIG_PREPARE_DONE))
+ break;
+ DELAY(10);
+ }
+ if (ntries == 15000)
+ return ETIMEDOUT;
+
+ /* Hardware should be ready now. */
+ IWN_SETBITS(sc, IWN_HW_IF_CONFIG, IWN_HW_IF_CONFIG_NIC_READY);
+ for (ntries = 0; ntries < 5; ntries++) {
+ if (IWN_READ(sc, IWN_HW_IF_CONFIG) &
+ IWN_HW_IF_CONFIG_NIC_READY)
+ return 0;
+ DELAY(10);
+ }
+ return ETIMEDOUT;
+}
+
+static int
+iwn_hw_init(struct iwn_softc *sc)
+{
+ struct iwn_ops *ops = &sc->ops;
+ int error, chnl, qid;
+ clock_t clk;
+ uint32_t rx_config;
+
+ ASSERT(mutex_owned(&sc->sc_mtx));
+
+ /* Clear pending interrupts. */
+ IWN_WRITE(sc, IWN_INT, 0xffffffff);
+
+ if ((error = iwn_apm_init(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not power ON adapter");
+ return error;
+ }
+
+ /* Select VMAIN power source. */
+ if ((error = iwn_nic_lock(sc)) != 0)
+ return error;
+ iwn_prph_clrbits(sc, IWN_APMG_PS, IWN_APMG_PS_PWR_SRC_MASK);
+ iwn_nic_unlock(sc);
+
+ /* Perform adapter-specific initialization. */
+ if ((error = ops->nic_config(sc)) != 0)
+ return error;
+
+ /* Initialize RX ring. */
+ if ((error = iwn_nic_lock(sc)) != 0)
+ return error;
+ IWN_WRITE(sc, IWN_FH_RX_CONFIG, 0);
+ IWN_WRITE(sc, IWN_FH_RX_WPTR, 0);
+ /* Set physical address of RX ring (256-byte aligned). */
+ IWN_WRITE(sc, IWN_FH_RX_BASE, sc->rxq.desc_dma.paddr >> 8);
+ /* Set physical address of RX status (16-byte aligned). */
+ IWN_WRITE(sc, IWN_FH_STATUS_WPTR, sc->rxq.stat_dma.paddr >> 4);
+ /* Enable RX. */
+ rx_config =
+ IWN_FH_RX_CONFIG_ENA |
+#if IWN_RBUF_SIZE == 8192
+ IWN_FH_RX_CONFIG_RB_SIZE_8K |
+#endif
+ IWN_FH_RX_CONFIG_IGN_RXF_EMPTY | /* HW bug workaround */
+ IWN_FH_RX_CONFIG_IRQ_DST_HOST |
+ IWN_FH_RX_CONFIG_SINGLE_FRAME |
+ IWN_FH_RX_CONFIG_RB_TIMEOUT(0) |
+ IWN_FH_RX_CONFIG_NRBD(IWN_RX_RING_COUNT_LOG);
+ IWN_WRITE(sc, IWN_FH_RX_CONFIG, rx_config);
+ iwn_nic_unlock(sc);
+ IWN_WRITE(sc, IWN_FH_RX_WPTR, (IWN_RX_RING_COUNT - 1) & ~7);
+
+ if ((error = iwn_nic_lock(sc)) != 0)
+ return error;
+
+ /* Initialize TX scheduler. */
+ iwn_prph_write(sc, sc->sched_txfact_addr, 0);
+
+ /* Set physical address of "keep warm" page (16-byte aligned). */
+ IWN_WRITE(sc, IWN_FH_KW_ADDR, sc->kw_dma.paddr >> 4);
+
+ /* Initialize TX rings. */
+ for (qid = 0; qid < sc->ntxqs; qid++) {
+ struct iwn_tx_ring *txq = &sc->txq[qid];
+
+ /* Set physical address of TX ring (256-byte aligned). */
+ IWN_WRITE(sc, IWN_FH_CBBC_QUEUE(qid),
+ txq->desc_dma.paddr >> 8);
+ }
+ iwn_nic_unlock(sc);
+
+ /* Enable DMA channels. */
+ for (chnl = 0; chnl < sc->ndmachnls; chnl++) {
+ IWN_WRITE(sc, IWN_FH_TX_CONFIG(chnl),
+ IWN_FH_TX_CONFIG_DMA_ENA |
+ IWN_FH_TX_CONFIG_DMA_CREDIT_ENA);
+ }
+
+ /* Clear "radio off" and "commands blocked" bits. */
+ IWN_WRITE(sc, IWN_UCODE_GP1_CLR, IWN_UCODE_GP1_RFKILL);
+ IWN_WRITE(sc, IWN_UCODE_GP1_CLR, IWN_UCODE_GP1_CMD_BLOCKED);
+
+ /* Clear pending interrupts. */
+ IWN_WRITE(sc, IWN_INT, 0xffffffff);
+ /* Enable interrupt coalescing. */
+ IWN_WRITE(sc, IWN_INT_COALESCING, 512 / 32);
+ /* Enable interrupts. */
+ IWN_WRITE(sc, IWN_INT_MASK, sc->int_mask);
+
+ /* _Really_ make sure "radio off" bit is cleared! */
+ IWN_WRITE(sc, IWN_UCODE_GP1_CLR, IWN_UCODE_GP1_RFKILL);
+ IWN_WRITE(sc, IWN_UCODE_GP1_CLR, IWN_UCODE_GP1_RFKILL);
+
+ /* Enable shadow registers. */
+ if (sc->hw_type >= IWN_HW_REV_TYPE_6000)
+ IWN_SETBITS(sc, IWN_SHADOW_REG_CTRL, 0x800fffff);
+
+ if ((error = ops->load_firmware(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!could not load firmware");
+ return error;
+ }
+ /* Wait at most one second for firmware alive notification. */
+ clk = ddi_get_lbolt() + drv_usectohz(1000000);
+ while ((sc->sc_flags & IWN_FLAG_FW_ALIVE) == 0) {
+ if (cv_timedwait(&sc->sc_alive_cv, &sc->sc_mtx, clk) < 0) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!timeout waiting for adapter to initialize");
+ return (IWN_FAIL);
+ }
+ }
+ /* Do post-firmware initialization. */
+ return ops->post_alive(sc);
+}
+
+static void
+iwn_hw_stop(struct iwn_softc *sc, boolean_t lock)
+{
+ int chnl, qid, ntries;
+
+ if (lock) {
+ mutex_enter(&sc->sc_mtx);
+ }
+
+ IWN_WRITE(sc, IWN_RESET, IWN_RESET_NEVO);
+
+ /* Disable interrupts. */
+ IWN_WRITE(sc, IWN_INT_MASK, 0);
+ IWN_WRITE(sc, IWN_INT, 0xffffffff);
+ IWN_WRITE(sc, IWN_FH_INT, 0xffffffff);
+ sc->sc_flags &= ~IWN_FLAG_USE_ICT;
+
+ /* Make sure we no longer hold the NIC lock. */
+ iwn_nic_unlock(sc);
+
+ /* Stop TX scheduler. */
+ iwn_prph_write(sc, sc->sched_txfact_addr, 0);
+
+ /* Stop all DMA channels. */
+ if (iwn_nic_lock(sc) == 0) {
+ for (chnl = 0; chnl < sc->ndmachnls; chnl++) {
+ IWN_WRITE(sc, IWN_FH_TX_CONFIG(chnl), 0);
+ for (ntries = 0; ntries < 200; ntries++) {
+ if (IWN_READ(sc, IWN_FH_TX_STATUS) &
+ IWN_FH_TX_STATUS_IDLE(chnl))
+ break;
+ DELAY(10);
+ }
+ }
+ iwn_nic_unlock(sc);
+ }
+
+ /* Stop RX ring. */
+ iwn_reset_rx_ring(sc, &sc->rxq);
+
+ /* Reset all TX rings. */
+ for (qid = 0; qid < sc->ntxqs; qid++)
+ iwn_reset_tx_ring(sc, &sc->txq[qid]);
+
+ if (iwn_nic_lock(sc) == 0) {
+ iwn_prph_write(sc, IWN_APMG_CLK_DIS,
+ IWN_APMG_CLK_CTRL_DMA_CLK_RQT);
+ iwn_nic_unlock(sc);
+ }
+ DELAY(5);
+ /* Power OFF adapter. */
+ iwn_apm_stop(sc);
+
+ sc->sc_flags &= ~(IWN_FLAG_HW_INITED | IWN_FLAG_FW_ALIVE);
+
+ if (lock) {
+ mutex_exit(&sc->sc_mtx);
+ }
+}
+
+static int
+iwn_init(struct iwn_softc *sc)
+{
+ int error;
+
+ mutex_enter(&sc->sc_mtx);
+ if (sc->sc_flags & IWN_FLAG_HW_INITED)
+ goto out;
+ if ((error = iwn_hw_prepare(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN, "!hardware not ready");
+ goto fail;
+ }
+
+ /* Check that the radio is not disabled by hardware switch. */
+ if (!(IWN_READ(sc, IWN_GP_CNTRL) & IWN_GP_CNTRL_RFKILL)) {
+ dev_err(sc->sc_dip, CE_WARN,
+ "!radio is disabled by hardware switch");
+ error = EPERM; /* :-) */
+ goto fail;
+ }
+
+ /* Read firmware images from the filesystem. */
+ if ((error = iwn_read_firmware(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN, "!could not read firmware");
+ goto fail;
+ }
+
+ /* Initialize interrupt mask to default value. */
+ sc->int_mask = IWN_INT_MASK_DEF;
+ sc->sc_flags &= ~IWN_FLAG_USE_ICT;
+
+ /* Initialize hardware and upload firmware. */
+ ASSERT(sc->fw.data != NULL && sc->fw.size > 0);
+ error = iwn_hw_init(sc);
+ if (error != 0) {
+ dev_err(sc->sc_dip, CE_WARN, "!could not initialize hardware");
+ goto fail;
+ }
+
+ /* Configure adapter now that it is ready. */
+ if ((error = iwn_config(sc)) != 0) {
+ dev_err(sc->sc_dip, CE_WARN, "!could not configure device");
+ goto fail;
+ }
+
+ sc->sc_flags |= IWN_FLAG_HW_INITED;
+out:
+ mutex_exit(&sc->sc_mtx);
+ return 0;
+
+fail:
+ iwn_hw_stop(sc, B_FALSE);
+ mutex_exit(&sc->sc_mtx);
+ return error;
+}
+
+/*
+ * XXX code from usr/src/uts/common/io/net80211/net880211_output.c
+ * Copyright (c) 2001 Atsushi Onoe
+ * Copyright (c) 2002, 2003 Sam Leffler, Errno Consulting
+ * Copyright (c) 2007-2009 Damien Bergamini
+ * All rights reserved.
+ */
+
+/*
+ * Add SSID element to a frame
+ */
+static uint8_t *
+ieee80211_add_ssid(uint8_t *frm, const uint8_t *ssid, uint32_t len)
+{
+ *frm++ = IEEE80211_ELEMID_SSID;
+ *frm++ = (uint8_t)len;
+ bcopy(ssid, frm, len);
+ return (frm + len);
+}
+
+/*
+ * Add supported rates information element to a frame.
+ */
+static uint8_t *
+ieee80211_add_rates(uint8_t *frm, const struct ieee80211_rateset *rs)
+{
+ uint8_t nrates;
+
+ *frm++ = IEEE80211_ELEMID_RATES;
+ nrates = rs->ir_nrates;
+ if (nrates > IEEE80211_RATE_SIZE)
+ nrates = IEEE80211_RATE_SIZE;
+ *frm++ = nrates;
+ bcopy(rs->ir_rates, frm, nrates);
+ return (frm + nrates);
+}
+
+/*
+ * Add extended supported rates element to a frame, usually for 11g mode
+ */
+static uint8_t *
+ieee80211_add_xrates(uint8_t *frm, const struct ieee80211_rateset *rs)
+{
+ if (rs->ir_nrates > IEEE80211_RATE_SIZE) {
+ uint8_t nrates = rs->ir_nrates - IEEE80211_RATE_SIZE;
+
+ *frm++ = IEEE80211_ELEMID_XRATES;
+ *frm++ = nrates;
+ bcopy(rs->ir_rates + IEEE80211_RATE_SIZE, frm, nrates);
+ frm += nrates;
+ }
+ return (frm);
+}
+
+/*
+ * XXX: Hack to set the current channel to the value advertised in beacons or
+ * probe responses. Only used during AP detection.
+ * XXX: Duplicated from if_iwi.c
+ */
+static void
+iwn_fix_channel(struct iwn_softc *sc, mblk_t *m,
+ struct iwn_rx_stat *stat)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_frame *wh;
+ uint8_t subtype;
+ uint8_t *frm, *efrm;
+
+ wh = (struct ieee80211_frame *)m->b_rptr;
+
+ if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) != IEEE80211_FC0_TYPE_MGT)
+ return;
+
+ subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
+
+ if (subtype != IEEE80211_FC0_SUBTYPE_BEACON &&
+ subtype != IEEE80211_FC0_SUBTYPE_PROBE_RESP)
+ return;
+
+ if (sc->sc_flags & IWN_FLAG_SCANNING_5GHZ) {
+ int chan = le16toh(stat->chan);
+ if (chan < __arraycount(ic->ic_sup_channels))
+ ic->ic_curchan = &ic->ic_sup_channels[chan];
+ return;
+ }
+
+ frm = (uint8_t *)(wh + 1);
+ efrm = (uint8_t *)m->b_wptr;
+
+ frm += 12; /* skip tstamp, bintval and capinfo fields */
+ while (frm < efrm) {
+ if (*frm == IEEE80211_ELEMID_DSPARMS)
+#if IEEE80211_CHAN_MAX < 255
+ if (frm[2] <= IEEE80211_CHAN_MAX)
+#endif
+ ic->ic_curchan = &ic->ic_sup_channels[frm[2]];
+
+ frm += frm[1] + 2;
+ }
+}
+
+/*
+ * invoked by GLD to start or open NIC
+ */
+static int
+iwn_m_start(void *arg)
+{
+ struct iwn_softc *sc;
+ ieee80211com_t *ic;
+ int err = IWN_FAIL;
+
+ sc = (struct iwn_softc *)arg;
+ ASSERT(sc != NULL);
+ ic = &sc->sc_ic;
+
+ err = iwn_init(sc);
+ if (err != IWN_SUCCESS) {
+ /*
+ * If initialization failed because the RF switch is off,
+ * return success anyway to make the 'plumb' succeed.
+ * The iwn_thread() tries to re-init background.
+ */
+ if (err == EPERM &&
+ !(IWN_READ(sc, IWN_GP_CNTRL) & IWN_GP_CNTRL_RFKILL)) {
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags |= IWN_FLAG_HW_ERR_RECOVER;
+ sc->sc_flags |= IWN_FLAG_RADIO_OFF;
+ mutex_exit(&sc->sc_mtx);
+ return (IWN_SUCCESS);
+ }
+
+ return (err);
+ }
+
+ ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
+
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags |= IWN_FLAG_RUNNING;
+ mutex_exit(&sc->sc_mtx);
+
+ return (IWN_SUCCESS);
+}
+
+/*
+ * invoked by GLD to stop or down NIC
+ */
+static void
+iwn_m_stop(void *arg)
+{
+ struct iwn_softc *sc;
+ ieee80211com_t *ic;
+
+ sc = (struct iwn_softc *)arg;
+ ASSERT(sc != NULL);
+ ic = &sc->sc_ic;
+
+ iwn_hw_stop(sc, B_TRUE);
+
+ /*
+ * release buffer for calibration
+ */
+
+ ieee80211_stop_watchdog(ic);
+ ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
+
+ mutex_enter(&sc->sc_mtx);
+ sc->sc_flags &= ~IWN_FLAG_HW_ERR_RECOVER;
+ sc->sc_flags &= ~IWN_FLAG_RATE_AUTO_CTL;
+
+ sc->sc_flags &= ~IWN_FLAG_RUNNING;
+ sc->sc_flags &= ~IWN_FLAG_SCANNING;
+ mutex_exit(&sc->sc_mtx);
+}
+
+
+/*
+ * Module Loading Data & Entry Points
+ */
+DDI_DEFINE_STREAM_OPS(iwn_devops, nulldev, nulldev, iwn_attach,
+ iwn_detach, nodev, NULL, D_MP, NULL, iwn_quiesce);
+
+static struct modldrv iwn_modldrv = {
+ &mod_driverops,
+ "Intel WiFi Link 4965 and 1000/5000/6000 series driver",
+ &iwn_devops
+};
+
+static struct modlinkage iwn_modlinkage = {
+ MODREV_1,
+ &iwn_modldrv,
+ NULL
+};
+
+int
+_init(void)
+{
+ int status;
+
+ status = ddi_soft_state_init(&iwn_state,
+ sizeof (struct iwn_softc), 1);
+ if (status != DDI_SUCCESS)
+ return (status);
+
+ mac_init_ops(&iwn_devops, "iwn");
+ status = mod_install(&iwn_modlinkage);
+ if (status != DDI_SUCCESS) {
+ mac_fini_ops(&iwn_devops);
+ ddi_soft_state_fini(&iwn_state);
+ }
+
+ return (status);
+}
+
+int
+_fini(void)
+{
+ int status;
+
+ status = mod_remove(&iwn_modlinkage);
+ if (status == DDI_SUCCESS) {
+ mac_fini_ops(&iwn_devops);
+ ddi_soft_state_fini(&iwn_state);
+ }
+
+ return (status);
+}
+
+int
+_info(struct modinfo *mip)
+{
+ return (mod_info(&iwn_modlinkage, mip));
+}
diff --git a/usr/src/uts/common/io/iwn/if_iwncompat.h b/usr/src/uts/common/io/iwn/if_iwncompat.h
new file mode 100644
index 0000000000..4e326bd2f7
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/if_iwncompat.h
@@ -0,0 +1,162 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+ */
+
+#ifndef _IF_IWNCOMPAT_H
+#define _IF_IWNCOMPAT_H
+
+/* XXX Added for NetBSD */
+#define IEEE80211_NO_HT
+
+/*
+ * QoS definitions
+ */
+
+#define AC_NUM (4) /* the number of access category */
+
+/*
+ * index of every AC in firmware
+ */
+#define QOS_AC_BK (0)
+#define QOS_AC_BE (1)
+#define QOS_AC_VI (2)
+#define QOS_AC_VO (3)
+#define QOS_AC_INVALID (-1)
+
+#define QOS_CW_RANGE_MIN (0) /* exponential of 2 */
+#define QOS_CW_RANGE_MAX (15) /* exponential of 2 */
+#define QOS_TXOP_MIN (0) /* unit of 32 microsecond */
+#define QOS_TXOP_MAX (255) /* unit of 32 microsecond */
+#define QOS_AIFSN_MIN (2)
+#define QOS_AIFSN_MAX (15) /* undefined */
+
+/*
+ * masks for flags of QoS parameter command
+ */
+#define QOS_PARAM_FLG_UPDATE_EDCA (0x01)
+#define QOS_PARAM_FLG_TGN (0x02)
+
+/*
+ * index of TX queue for every AC
+ */
+#define QOS_AC_BK_TO_TXQ (3)
+#define QOS_AC_BE_TO_TXQ (2)
+#define QOS_AC_VI_TO_TXQ (1)
+#define QOS_AC_VO_TO_TXQ (0)
+#define TXQ_FOR_AC_MIN (0)
+#define TXQ_FOR_AC_MAX (3)
+#define TXQ_FOR_AC_INVALID (-1)
+#define NON_QOS_TXQ QOS_AC_BE_TO_TXQ
+#define QOS_TXQ_FOR_MGT QOS_AC_VO_TO_TXQ
+
+#define WME_TID_MIN (0)
+#define WME_TID_MAX (7)
+#define WME_TID_INVALID ((uint8_t)-1)
+
+#define PCI_VENDOR_INTEL 0x8086 /* Intel */
+
+/* WiFi Link 1000 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_1000_1 0x0083
+#define PCI_PRODUCT_INTEL_WIFI_LINK_1000_2 0x0084
+
+/* Centrino Wireless-N 100 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_100_1 0x08ae
+#define PCI_PRODUCT_INTEL_WIFI_LINK_100_2 0x08af
+
+/* Centrino Wireless-N 105 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_105_1 0x0894
+#define PCI_PRODUCT_INTEL_WIFI_LINK_105_2 0x0895
+
+/* Centrino Wireless-N 130 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_130_1 0x0896
+#define PCI_PRODUCT_INTEL_WIFI_LINK_130_2 0x0897
+
+/* Centrino Wireless-N 135 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_135_1 0x0892
+#define PCI_PRODUCT_INTEL_WIFI_LINK_135_2 0x0893
+
+/* Centrino Wireless-N 1030 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_1030_1 0x008a
+#define PCI_PRODUCT_INTEL_WIFI_LINK_1030_2 0x008b
+
+/* Centrino Wireless-N 2200 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_2200_1 0x0890
+#define PCI_PRODUCT_INTEL_WIFI_LINK_2200_2 0x0891
+
+/* Centrino Wireless-N 2230 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_2230_1 0x0887
+#define PCI_PRODUCT_INTEL_WIFI_LINK_2230_2 0x0888
+
+/* Wireless WiFi Link 4965 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_4965_1 0x4229
+#define PCI_PRODUCT_INTEL_WIFI_LINK_4965_2 0x4230
+#define PCI_PRODUCT_INTEL_WIFI_LINK_4965_3 0x422d
+#define PCI_PRODUCT_INTEL_WIFI_LINK_4965_4 0x4233
+
+/* WiFi Link 5100 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_5100_1 0x4232
+#define PCI_PRODUCT_INTEL_WIFI_LINK_5100_2 0x4237
+
+/* WiFi Link 5150 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_5150_1 0x423c
+#define PCI_PRODUCT_INTEL_WIFI_LINK_5150_2 0x423d
+
+/* WiFi Link 5300 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_5300_1 0x4235
+#define PCI_PRODUCT_INTEL_WIFI_LINK_5300_2 0x4236
+
+/* WiFi Link 5350 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_5350_1 0x423a
+#define PCI_PRODUCT_INTEL_WIFI_LINK_5350_2 0x423b
+
+/* Centrino Advanced-N 6200 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_6000_IPA_1 0x422c
+#define PCI_PRODUCT_INTEL_WIFI_LINK_6000_IPA_2 0x4239
+
+/* Centrino Advanced-N 6205 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_6005_2X2_1 0x0082
+#define PCI_PRODUCT_INTEL_WIFI_LINK_6005_2X2_2 0x0085
+
+/* Centrino Advanced-N 6230 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_6230_1 0x0090
+#define PCI_PRODUCT_INTEL_WIFI_LINK_6230_2 0x0091
+
+/* Centrino Advanced-N 6235 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_6235 0x088e
+#define PCI_PRODUCT_INTEL_WIFI_LINK_6235_2 0x088f
+
+/* Centrino Advanced-N 6250 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_6050_2X2_1 0x0087
+#define PCI_PRODUCT_INTEL_WIFI_LINK_6050_2X2_2 0x0089
+
+/* Centrino Ultimate-N 6300 */
+#define PCI_PRODUCT_INTEL_WIFI_LINK_6000_3X3_1 0x422b
+#define PCI_PRODUCT_INTEL_WIFI_LINK_6000_3X3_2 0x4238
+
+#define __inline inline
+#define __packed __attribute__((packed))
+#define __arraycount(x) ARRAY_SIZE(x)
+#define abs(x) ABS(x)
+
+#define le16toh(x) LE_16(x)
+#define htole16(x) LE_16(x)
+#define le32toh(x) LE_32(x)
+#define htole32(x) LE_32(x)
+#define le64toh(x) LE_64(x)
+#define htole64(x) LE_64(x)
+
+#define IWN_SUCCESS 0
+#define IWN_FAIL EIO
+
+#endif /* _IF_IWNCOMPAT_H */
diff --git a/usr/src/uts/common/io/iwn/if_iwnreg.h b/usr/src/uts/common/io/iwn/if_iwnreg.h
new file mode 100644
index 0000000000..78bfc3088f
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/if_iwnreg.h
@@ -0,0 +1,2006 @@
+/* $NetBSD: if_iwnreg.h,v 1.15 2014/11/09 14:40:54 nonaka Exp $ */
+/* $OpenBSD: if_iwnreg.h,v 1.49 2014/09/09 18:56:24 sthen Exp $ */
+
+/*-
+ * Copyright (c) 2007, 2008
+ * Damien Bergamini <damien.bergamini@free.fr>
+ *
+ * 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.
+ */
+
+/*
+ * Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+ */
+
+#ifndef _IF_IWNREG_H
+#define _IF_IWNREG_H
+
+/* XXX Added for NetBSD */
+#define IEEE80211_TKIP_MICLEN 8
+
+#define IWN_TX_RING_COUNT 256
+#define IWN_TX_RING_LOMARK 192
+#define IWN_TX_RING_HIMARK 224
+#define IWN_RX_RING_COUNT_LOG 6
+#define IWN_RX_RING_COUNT (1 << IWN_RX_RING_COUNT_LOG)
+
+#define IWN4965_NTXQUEUES 16
+#define IWN5000_NTXQUEUES 20
+
+#define IWN_CMD_QUEUE_NUM 4
+
+#define IWN4965_NDMACHNLS 7
+#define IWN5000_NDMACHNLS 8
+
+#define IWN_SRVC_DMACHNL 9
+
+#define IWN_KW_SIZE 4096
+
+#define IWN_ICT_SIZE 4096
+#define IWN_ICT_COUNT (IWN_ICT_SIZE / sizeof (uint32_t))
+
+/* Maximum number of DMA segments for TX. */
+#define IWN_MAX_SCATTER 20
+
+/* RX buffers must be large enough to hold a full 4K A-MPDU. */
+#define IWN_RBUF_SIZE (4 * 1024)
+
+#define IWN_TBUF_SIZE (4 * 1024)
+
+
+#if defined(_LP64)
+/* HW supports 36-bit DMA addresses. */
+#define IWN_LOADDR(paddr) ((uint32_t)(paddr))
+#define IWN_HIADDR(paddr) (((paddr) >> 32) & 0xf)
+#else
+#define IWN_LOADDR(paddr) (paddr)
+#define IWN_HIADDR(paddr) (0)
+#endif
+
+/* Base Address Register. */
+#define IWN_PCI_BAR0 PCI_MAPREG_START
+
+/*
+ * Control and status registers.
+ */
+#define IWN_HW_IF_CONFIG 0x000
+#define IWN_INT_COALESCING 0x004
+#define IWN_INT_PERIODIC 0x005 /* use IWN_WRITE_1 */
+#define IWN_INT 0x008
+#define IWN_INT_MASK 0x00c
+#define IWN_FH_INT 0x010
+#define IWN_RESET 0x020
+#define IWN_GP_CNTRL 0x024
+#define IWN_HW_REV 0x028
+#define IWN_EEPROM 0x02c
+#define IWN_EEPROM_GP 0x030
+#define IWN_OTP_GP 0x034
+#define IWN_GIO 0x03c
+#define IWN_GP_DRIVER 0x050
+#define IWN_UCODE_GP1_CLR 0x05c
+#define IWN_LED 0x094
+#define IWN_DRAM_INT_TBL 0x0a0
+#define IWN_SHADOW_REG_CTRL 0x0a8
+#define IWN_GIO_CHICKEN 0x100
+#define IWN_ANA_PLL 0x20c
+#define IWN_HW_REV_WA 0x22c
+#define IWN_DBG_HPET_MEM 0x240
+#define IWN_DBG_LINK_PWR_MGMT 0x250
+#define IWN_MEM_RADDR 0x40c
+#define IWN_MEM_WADDR 0x410
+#define IWN_MEM_WDATA 0x418
+#define IWN_MEM_RDATA 0x41c
+#define IWN_PRPH_WADDR 0x444
+#define IWN_PRPH_RADDR 0x448
+#define IWN_PRPH_WDATA 0x44c
+#define IWN_PRPH_RDATA 0x450
+#define IWN_HBUS_TARG_WRPTR 0x460
+
+/*
+ * Flow-Handler registers.
+ */
+#define IWN_FH_TFBD_CTRL0(qid) (0x1900 + (qid) * 8)
+#define IWN_FH_TFBD_CTRL1(qid) (0x1904 + (qid) * 8)
+#define IWN_FH_KW_ADDR 0x197c
+#define IWN_FH_SRAM_ADDR(qid) (0x19a4 + (qid) * 4)
+#define IWN_FH_CBBC_QUEUE(qid) (0x19d0 + (qid) * 4)
+#define IWN_FH_STATUS_WPTR 0x1bc0
+#define IWN_FH_RX_BASE 0x1bc4
+#define IWN_FH_RX_WPTR 0x1bc8
+#define IWN_FH_RX_CONFIG 0x1c00
+#define IWN_FH_RX_STATUS 0x1c44
+#define IWN_FH_TX_CONFIG(qid) (0x1d00 + (qid) * 32)
+#define IWN_FH_TXBUF_STATUS(qid) (0x1d08 + (qid) * 32)
+#define IWN_FH_TX_CHICKEN 0x1e98
+#define IWN_FH_TX_STATUS 0x1eb0
+
+/*
+ * TX scheduler registers.
+ */
+#define IWN_SCHED_BASE 0xa02c00
+#define IWN_SCHED_SRAM_ADDR (IWN_SCHED_BASE + 0x000)
+#define IWN5000_SCHED_DRAM_ADDR (IWN_SCHED_BASE + 0x008)
+#define IWN4965_SCHED_DRAM_ADDR (IWN_SCHED_BASE + 0x010)
+#define IWN5000_SCHED_TXFACT (IWN_SCHED_BASE + 0x010)
+#define IWN4965_SCHED_TXFACT (IWN_SCHED_BASE + 0x01c)
+#define IWN4965_SCHED_QUEUE_RDPTR(qid) (IWN_SCHED_BASE + 0x064 + (qid) * 4)
+#define IWN5000_SCHED_QUEUE_RDPTR(qid) (IWN_SCHED_BASE + 0x068 + (qid) * 4)
+#define IWN4965_SCHED_QCHAIN_SEL (IWN_SCHED_BASE + 0x0d0)
+#define IWN4965_SCHED_INTR_MASK (IWN_SCHED_BASE + 0x0e4)
+#define IWN5000_SCHED_QCHAIN_SEL (IWN_SCHED_BASE + 0x0e8)
+#define IWN4965_SCHED_QUEUE_STATUS(qid) (IWN_SCHED_BASE + 0x104 + (qid) * 4)
+#define IWN5000_SCHED_INTR_MASK (IWN_SCHED_BASE + 0x108)
+#define IWN5000_SCHED_QUEUE_STATUS(qid) (IWN_SCHED_BASE + 0x10c + (qid) * 4)
+#define IWN5000_SCHED_AGGR_SEL (IWN_SCHED_BASE + 0x248)
+
+/*
+ * Offsets in TX scheduler's SRAM.
+ */
+#define IWN4965_SCHED_CTX_OFF 0x380
+#define IWN4965_SCHED_CTX_LEN 416
+#define IWN4965_SCHED_QUEUE_OFFSET(qid) (0x380 + (qid) * 8)
+#define IWN4965_SCHED_TRANS_TBL(qid) (0x500 + (qid) * 2)
+#define IWN5000_SCHED_CTX_OFF 0x600
+#define IWN5000_SCHED_CTX_LEN 520
+#define IWN5000_SCHED_QUEUE_OFFSET(qid) (0x600 + (qid) * 8)
+#define IWN5000_SCHED_TRANS_TBL(qid) (0x7e0 + (qid) * 2)
+
+/*
+ * NIC internal memory offsets.
+ */
+#define IWN_APMG_CLK_CTRL 0x3000
+#define IWN_APMG_CLK_EN 0x3004
+#define IWN_APMG_CLK_DIS 0x3008
+#define IWN_APMG_PS 0x300c
+#define IWN_APMG_DIGITAL_SVR 0x3058
+#define IWN_APMG_ANALOG_SVR 0x306c
+#define IWN_APMG_PCI_STT 0x3010
+#define IWN_BSM_WR_CTRL 0x3400
+#define IWN_BSM_WR_MEM_SRC 0x3404
+#define IWN_BSM_WR_MEM_DST 0x3408
+#define IWN_BSM_WR_DWCOUNT 0x340c
+#define IWN_BSM_DRAM_TEXT_ADDR 0x3490
+#define IWN_BSM_DRAM_TEXT_SIZE 0x3494
+#define IWN_BSM_DRAM_DATA_ADDR 0x3498
+#define IWN_BSM_DRAM_DATA_SIZE 0x349c
+#define IWN_BSM_SRAM_BASE 0x3800
+
+/* Possible flags for register IWN_HW_IF_CONFIG. */
+#define IWN_HW_IF_CONFIG_4965_R (1 << 4)
+#define IWN_HW_IF_CONFIG_MAC_SI (1 << 8)
+#define IWN_HW_IF_CONFIG_RADIO_SI (1 << 9)
+#define IWN_HW_IF_CONFIG_EEPROM_LOCKED (1 << 21)
+#define IWN_HW_IF_CONFIG_NIC_READY (1 << 22)
+#define IWN_HW_IF_CONFIG_HAP_WAKE_L1A (1 << 23)
+#define IWN_HW_IF_CONFIG_PREPARE_DONE (1 << 25)
+#define IWN_HW_IF_CONFIG_PREPARE (1 << 27)
+
+/* Possible values for register IWN_INT_PERIODIC. */
+#define IWN_INT_PERIODIC_DIS 0x00
+#define IWN_INT_PERIODIC_ENA 0xff
+
+/* Possible flags for registers IWN_PRPH_RADDR/IWN_PRPH_WADDR. */
+#define IWN_PRPH_DWORD ((sizeof (uint32_t) - 1) << 24)
+
+/* Possible values for IWN_BSM_WR_MEM_DST. */
+#define IWN_FW_TEXT_BASE 0x00000000
+#define IWN_FW_DATA_BASE 0x00800000
+
+/* Possible flags for register IWN_RESET. */
+#define IWN_RESET_NEVO (1U << 0)
+#define IWN_RESET_SW (1U << 7)
+#define IWN_RESET_MASTER_DISABLED (1U << 8)
+#define IWN_RESET_STOP_MASTER (1U << 9)
+#define IWN_RESET_LINK_PWR_MGMT_DIS (1U << 31)
+
+/* Possible flags for register IWN_GP_CNTRL. */
+#define IWN_GP_CNTRL_MAC_ACCESS_ENA (1 << 0)
+#define IWN_GP_CNTRL_MAC_CLOCK_READY (1 << 0)
+#define IWN_GP_CNTRL_INIT_DONE (1 << 2)
+#define IWN_GP_CNTRL_MAC_ACCESS_REQ (1 << 3)
+#define IWN_GP_CNTRL_SLEEP (1 << 4)
+#define IWN_GP_CNTRL_RFKILL (1 << 27)
+
+/* Possible flags for register IWN_HW_REV. */
+#define IWN_HW_REV_TYPE_SHIFT 4
+#define IWN_HW_REV_TYPE_MASK 0x000001f0
+#define IWN_HW_REV_TYPE_4965 0
+#define IWN_HW_REV_TYPE_5300 2
+#define IWN_HW_REV_TYPE_5350 3
+#define IWN_HW_REV_TYPE_5150 4
+#define IWN_HW_REV_TYPE_5100 5
+#define IWN_HW_REV_TYPE_1000 6
+#define IWN_HW_REV_TYPE_6000 7
+#define IWN_HW_REV_TYPE_6050 8
+#define IWN_HW_REV_TYPE_6005 11
+/* Types 6030 and 6035 also return 11 */
+#define IWN_HW_REV_TYPE_2030 12
+#define IWN_HW_REV_TYPE_2000 16
+#define IWN_HW_REV_TYPE_105 17
+#define IWN_HW_REV_TYPE_135 18
+
+/* Possible flags for register IWN_GIO_CHICKEN. */
+#define IWN_GIO_CHICKEN_L1A_NO_L0S_RX (1 << 23)
+#define IWN_GIO_CHICKEN_DIS_L0S_TIMER (1 << 29)
+
+/* Possible flags for register IWN_GIO. */
+#define IWN_GIO_L0S_ENA (1 << 1)
+
+/* Possible flags for register IWN_GP_DRIVER. */
+#define IWN_GP_DRIVER_RADIO_3X3_HYB (0 << 0)
+#define IWN_GP_DRIVER_RADIO_2X2_HYB (1 << 0)
+#define IWN_GP_DRIVER_RADIO_2X2_IPA (2 << 0)
+#define IWN_GP_DRIVER_CALIB_VER6 (1 << 2)
+#define IWN_GP_DRIVER_6050_1X2 (1 << 3)
+#define IWN_GP_DRIVER_RADIO_IQ_INVERT (1 << 7)
+
+/* Possible flags for register IWN_UCODE_GP1_CLR. */
+#define IWN_UCODE_GP1_RFKILL (1 << 1)
+#define IWN_UCODE_GP1_CMD_BLOCKED (1 << 2)
+#define IWN_UCODE_GP1_CTEMP_STOP_RF (1 << 3)
+
+/* Possible flags/values for register IWN_LED. */
+#define IWN_LED_BSM_CTRL (1 << 5)
+#define IWN_LED_OFF 0x00000038
+#define IWN_LED_ON 0x00000078
+
+/* Possible flags for register IWN_DRAM_INT_TBL. */
+#define IWN_DRAM_INT_TBL_WRAP_CHECK (1 << 27)
+#define IWN_DRAM_INT_TBL_ENABLE (1 << 31)
+
+/* Possible values for register IWN_ANA_PLL. */
+#define IWN_ANA_PLL_INIT 0x00880300
+
+/* Possible flags for register IWN_FH_RX_STATUS. */
+#define IWN_FH_RX_STATUS_IDLE (1 << 24)
+
+/* Possible flags for register IWN_BSM_WR_CTRL. */
+#define IWN_BSM_WR_CTRL_START_EN (1 << 30)
+#define IWN_BSM_WR_CTRL_START (1 << 31)
+
+/* Possible flags for register IWN_INT. */
+#define IWN_INT_ALIVE (1 << 0)
+#define IWN_INT_WAKEUP (1 << 1)
+#define IWN_INT_SW_RX (1 << 3)
+#define IWN_INT_CT_REACHED (1 << 6)
+#define IWN_INT_RF_TOGGLED (1 << 7)
+#define IWN_INT_SW_ERR (1 << 25)
+#define IWN_INT_SCHED (1 << 26)
+#define IWN_INT_FH_TX (1 << 27)
+#define IWN_INT_RX_PERIODIC (1 << 28)
+#define IWN_INT_HW_ERR (1 << 29)
+#define IWN_INT_FH_RX (1U << 31)
+
+/* Shortcut. */
+#define IWN_INT_MASK_DEF \
+ (IWN_INT_SW_ERR | IWN_INT_HW_ERR | IWN_INT_FH_TX | \
+ IWN_INT_FH_RX | IWN_INT_ALIVE | IWN_INT_WAKEUP | \
+ IWN_INT_SW_RX | IWN_INT_CT_REACHED | IWN_INT_RF_TOGGLED)
+
+/* Possible flags for register IWN_FH_INT. */
+#define IWN_FH_INT_TX_CHNL(x) (1 << (x))
+#define IWN_FH_INT_RX_CHNL(x) (1 << ((x) + 16))
+#define IWN_FH_INT_HI_PRIOR (1 << 30)
+/* Shortcuts for the above. */
+#define IWN_FH_INT_TX \
+ (IWN_FH_INT_TX_CHNL(0) | IWN_FH_INT_TX_CHNL(1))
+#define IWN_FH_INT_RX \
+ (IWN_FH_INT_RX_CHNL(0) | IWN_FH_INT_RX_CHNL(1) | IWN_FH_INT_HI_PRIOR)
+
+/* Possible flags/values for register IWN_FH_TX_CONFIG. */
+#define IWN_FH_TX_CONFIG_DMA_PAUSE 0
+#define IWN_FH_TX_CONFIG_DMA_ENA (1U << 31)
+#define IWN_FH_TX_CONFIG_CIRQ_HOST_ENDTFD (1U << 20)
+
+/* Possible flags/values for register IWN_FH_TXBUF_STATUS. */
+#define IWN_FH_TXBUF_STATUS_TBNUM(x) ((x) << 20)
+#define IWN_FH_TXBUF_STATUS_TBIDX(x) ((x) << 12)
+#define IWN_FH_TXBUF_STATUS_TFBD_VALID 3
+
+/* Possible flags for register IWN_FH_TX_CHICKEN. */
+#define IWN_FH_TX_CHICKEN_SCHED_RETRY (1 << 1)
+
+/* Possible flags for register IWN_FH_TX_STATUS. */
+#define IWN_FH_TX_STATUS_IDLE(chnl) (1 << ((chnl) + 16))
+
+/* Possible flags for register IWN_FH_RX_CONFIG. */
+#define IWN_FH_RX_CONFIG_ENA (1U << 31)
+#define IWN_FH_RX_CONFIG_NRBD(x) ((x) << 20)
+#define IWN_FH_RX_CONFIG_RB_SIZE_8K (1U << 16)
+#define IWN_FH_RX_CONFIG_SINGLE_FRAME (1U << 15)
+#define IWN_FH_RX_CONFIG_IRQ_DST_HOST (1U << 12)
+#define IWN_FH_RX_CONFIG_RB_TIMEOUT(x) ((x) << 4)
+#define IWN_FH_RX_CONFIG_IGN_RXF_EMPTY (1U << 2)
+
+/* Possible flags for register IWN_FH_TX_CONFIG. */
+#define IWN_FH_TX_CONFIG_DMA_ENA (1U << 31)
+#define IWN_FH_TX_CONFIG_DMA_CREDIT_ENA (1U << 3)
+
+/* Possible flags for register IWN_EEPROM. */
+#define IWN_EEPROM_READ_VALID (1 << 0)
+#define IWN_EEPROM_CMD (1 << 1)
+
+/* Possible flags for register IWN_EEPROM_GP. */
+#define IWN_EEPROM_GP_IF_OWNER 0x00000180
+
+/* Possible flags for register IWN_OTP_GP. */
+#define IWN_OTP_GP_DEV_SEL_OTP (1 << 16)
+#define IWN_OTP_GP_RELATIVE_ACCESS (1 << 17)
+#define IWN_OTP_GP_ECC_CORR_STTS (1 << 20)
+#define IWN_OTP_GP_ECC_UNCORR_STTS (1 << 21)
+
+/* Possible flags for register IWN_SCHED_QUEUE_STATUS. */
+#define IWN4965_TXQ_STATUS_ACTIVE 0x0007fc01
+#define IWN4965_TXQ_STATUS_INACTIVE 0x0007fc00
+#define IWN4965_TXQ_STATUS_AGGR_ENA (1 << 5 | 1 << 8)
+#define IWN4965_TXQ_STATUS_CHGACT (1 << 10)
+#define IWN5000_TXQ_STATUS_ACTIVE 0x00ff0018
+#define IWN5000_TXQ_STATUS_INACTIVE 0x00ff0010
+#define IWN5000_TXQ_STATUS_CHGACT (1 << 19)
+
+/* Possible flags for registers IWN_APMG_CLK_*. */
+#define IWN_APMG_CLK_CTRL_DMA_CLK_RQT (1 << 9)
+#define IWN_APMG_CLK_CTRL_BSM_CLK_RQT (1 << 11)
+
+/* Possible flags for register IWN_APMG_PS. */
+#define IWN_APMG_PS_EARLY_PWROFF_DIS (1 << 22)
+#define IWN_APMG_PS_PWR_SRC(x) ((x) << 24)
+#define IWN_APMG_PS_PWR_SRC_VMAIN 0
+#define IWN_APMG_PS_PWR_SRC_VAUX 2
+#define IWN_APMG_PS_PWR_SRC_MASK IWN_APMG_PS_PWR_SRC(3)
+#define IWN_APMG_PS_RESET_REQ (1 << 26)
+
+/* Possible flags for register IWN_APMG_DIGITAL_SVR. */
+#define IWN_APMG_DIGITAL_SVR_VOLTAGE(x) (((x) & 0xf) << 5)
+#define IWN_APMG_DIGITAL_SVR_VOLTAGE_MASK \
+ IWN_APMG_DIGITAL_SVR_VOLTAGE(0xf)
+#define IWN_APMG_DIGITAL_SVR_VOLTAGE_1_32 \
+ IWN_APMG_DIGITAL_SVR_VOLTAGE(3)
+
+/* Possible flags for IWN_APMG_PCI_STT. */
+#define IWN_APMG_PCI_STT_L1A_DIS (1 << 11)
+
+/* Possible flags for register IWN_BSM_DRAM_TEXT_SIZE. */
+#define IWN_FW_UPDATED (1U << 31)
+
+#define IWN_SCHED_WINSZ 64
+#define IWN_SCHED_LIMIT 64
+#define IWN4965_SCHED_COUNT 512
+#define IWN5000_SCHED_COUNT (IWN_TX_RING_COUNT + IWN_SCHED_WINSZ)
+#define IWN4965_SCHEDSZ (IWN4965_NTXQUEUES * IWN4965_SCHED_COUNT * 2)
+#define IWN5000_SCHEDSZ (IWN5000_NTXQUEUES * IWN5000_SCHED_COUNT * 2)
+
+struct iwn_tx_desc {
+ uint8_t reserved1[3];
+ uint8_t nsegs;
+ struct {
+ uint32_t addr;
+ uint16_t len;
+ } __packed segs[IWN_MAX_SCATTER];
+ /* Pad to 128 bytes. */
+ uint32_t reserved2;
+} __packed;
+
+struct iwn_rx_status {
+ uint16_t closed_count;
+ uint16_t closed_rx_count;
+ uint16_t finished_count;
+ uint16_t finished_rx_count;
+ uint32_t reserved[2];
+} __packed;
+
+struct iwn_rx_desc {
+ uint32_t len;
+ uint8_t type;
+#define IWN_UC_READY 1
+#define IWN_ADD_NODE_DONE 24
+#define IWN_TX_DONE 28
+#define IWN5000_CALIBRATION_RESULT 102
+#define IWN5000_CALIBRATION_DONE 103
+#define IWN_START_SCAN 130
+#define IWN_STOP_SCAN 132
+#define IWN_RX_STATISTICS 156
+#define IWN_BEACON_STATISTICS 157
+#define IWN_STATE_CHANGED 161
+#define IWN_BEACON_MISSED 162
+#define IWN_RX_PHY 192
+#define IWN_MPDU_RX_DONE 193
+#define IWN_RX_DONE 195
+#define IWN_RX_COMPRESSED_BA 197
+
+ uint8_t flags;
+ uint8_t idx;
+ uint8_t qid;
+} __packed;
+
+/* Possible RX status flags. */
+#define IWN_RX_NO_CRC_ERR (1 << 0)
+#define IWN_RX_NO_OVFL_ERR (1 << 1)
+/* Shortcut for the above. */
+#define IWN_RX_NOERROR (IWN_RX_NO_CRC_ERR | IWN_RX_NO_OVFL_ERR)
+#define IWN_RX_MPDU_MIC_OK (1 << 6)
+#define IWN_RX_CIPHER_MASK (7 << 8)
+#define IWN_RX_CIPHER_CCMP (2 << 8)
+#define IWN_RX_MPDU_DEC (1 << 11)
+#define IWN_RX_DECRYPT_MASK (3 << 11)
+#define IWN_RX_DECRYPT_OK (3 << 11)
+
+struct iwn_tx_cmd {
+ uint8_t code;
+#define IWN_CMD_RXON 16
+#define IWN_CMD_RXON_ASSOC 17
+#define IWN_CMD_EDCA_PARAMS 19
+#define IWN_CMD_TIMING 20
+#define IWN_CMD_ADD_NODE 24
+#define IWN_CMD_TX_DATA 28
+#define IWN_CMD_SET_LED 72
+#define IWN_CMD_LINK_QUALITY 78
+#define IWN5000_CMD_WIMAX_COEX 90
+#define IWN5000_CMD_CALIB_CONFIG 101
+#define IWN_CMD_SET_POWER_MODE 119
+#define IWN_CMD_SCAN 128
+#define IWN_CMD_TXPOWER_DBM 149
+#define IWN_CMD_TXPOWER 151
+#define IWN5000_CMD_TX_ANT_CONFIG 152
+#define IWN_CMD_BT_COEX 155
+#define IWN_CMD_GET_STATISTICS 156
+#define IWN_CMD_SET_CRITICAL_TEMP 164
+#define IWN_CMD_SET_SENSITIVITY 168
+#define IWN_CMD_PHY_CALIB 176
+#define IWN_CMD_BT_COEX_PRIO_TABLE 204
+#define IWN_CMD_BT_COEX_PROT 205
+
+ uint8_t flags;
+ uint8_t idx;
+ uint8_t qid;
+ uint8_t data[136];
+} __packed;
+
+/* Antenna flags, used in various commands. */
+#define IWN_ANT_A (1 << 0)
+#define IWN_ANT_B (1 << 1)
+#define IWN_ANT_C (1 << 2)
+/* Shortcuts. */
+#define IWN_ANT_AB (IWN_ANT_A | IWN_ANT_B)
+#define IWN_ANT_BC (IWN_ANT_B | IWN_ANT_C)
+#define IWN_ANT_ABC (IWN_ANT_A | IWN_ANT_B | IWN_ANT_C)
+
+/* Structure for command IWN_CMD_RXON. */
+struct iwn_rxon {
+ uint8_t myaddr[IEEE80211_ADDR_LEN];
+ uint16_t reserved1;
+ uint8_t bssid[IEEE80211_ADDR_LEN];
+ uint16_t reserved2;
+ uint8_t wlap[IEEE80211_ADDR_LEN];
+ uint16_t reserved3;
+ uint8_t mode;
+#define IWN_MODE_HOSTAP 1
+#define IWN_MODE_STA 3
+#define IWN_MODE_IBSS 4
+#define IWN_MODE_MONITOR 6
+
+ uint8_t air;
+ uint16_t rxchain;
+#define IWN_RXCHAIN_DRIVER_FORCE (1 << 0)
+#define IWN_RXCHAIN_VALID(x) (((x) & IWN_ANT_ABC) << 1)
+#define IWN_RXCHAIN_FORCE_SEL(x) (((x) & IWN_ANT_ABC) << 4)
+#define IWN_RXCHAIN_FORCE_MIMO_SEL(x) (((x) & IWN_ANT_ABC) << 7)
+#define IWN_RXCHAIN_IDLE_COUNT(x) ((x) << 10)
+#define IWN_RXCHAIN_MIMO_COUNT(x) ((x) << 12)
+#define IWN_RXCHAIN_MIMO_FORCE (1 << 14)
+
+ uint8_t ofdm_mask;
+ uint8_t cck_mask;
+ uint16_t associd;
+ uint32_t flags;
+#define IWN_RXON_24GHZ (1 << 0)
+#define IWN_RXON_CCK (1 << 1)
+#define IWN_RXON_AUTO (1 << 2)
+#define IWN_RXON_SHSLOT (1 << 4)
+#define IWN_RXON_SHPREAMBLE (1 << 5)
+#define IWN_RXON_NODIVERSITY (1 << 7)
+#define IWN_RXON_ANTENNA_A (1 << 8)
+#define IWN_RXON_ANTENNA_B (1 << 9)
+#define IWN_RXON_TSF (1 << 15)
+#define IWN_RXON_CTS_TO_SELF (1 << 30)
+
+ uint32_t filter;
+#define IWN_FILTER_PROMISC (1 << 0)
+#define IWN_FILTER_CTL (1 << 1)
+#define IWN_FILTER_MULTICAST (1 << 2)
+#define IWN_FILTER_NODECRYPT (1 << 3)
+#define IWN_FILTER_MC_NODECRYPT (1 << 4)
+#define IWN_FILTER_BSS (1 << 5)
+#define IWN_FILTER_BEACON (1 << 6)
+
+ uint8_t chan;
+ uint8_t reserved4;
+ uint8_t ht_single_mask;
+ uint8_t ht_dual_mask;
+ /* The following fields are for >=5000 Series only. */
+ uint8_t ht_triple_mask;
+ uint8_t reserved5;
+ uint16_t acquisition;
+ uint16_t reserved6;
+} __packed;
+
+#define IWN4965_RXONSZ (sizeof (struct iwn_rxon) - 6)
+#define IWN5000_RXONSZ (sizeof (struct iwn_rxon))
+
+/* Structure for command IWN_CMD_ASSOCIATE. */
+struct iwn_assoc {
+ uint32_t flags;
+ uint32_t filter;
+ uint8_t ofdm_mask;
+ uint8_t cck_mask;
+ uint16_t reserved;
+} __packed;
+
+/* Structure for command IWN_CMD_EDCA_PARAMS. */
+struct iwn_edca_params {
+ uint32_t flags;
+#define IWN_EDCA_UPDATE (1 << 0)
+#define IWN_EDCA_TXOP (1 << 4)
+
+ struct {
+ uint16_t cwmin;
+ uint16_t cwmax;
+ uint8_t aifsn;
+ uint8_t reserved;
+ uint16_t txoplimit;
+ } __packed ac[WME_NUM_AC];
+} __packed;
+
+/* Structure for command IWN_CMD_TIMING. */
+struct iwn_cmd_timing {
+ uint64_t tstamp;
+ uint16_t bintval;
+ uint16_t atim;
+ uint32_t binitval;
+ uint16_t lintval;
+ uint16_t reserved;
+} __packed;
+
+/* Structure for command IWN_CMD_ADD_NODE. */
+struct iwn_node_info {
+ uint8_t control;
+#define IWN_NODE_UPDATE (1 << 0)
+
+ uint8_t reserved1[3];
+
+ uint8_t macaddr[IEEE80211_ADDR_LEN];
+ uint16_t reserved2;
+ uint8_t id;
+#define IWN_ID_BSS 0
+#define IWN5000_ID_BROADCAST 15
+#define IWN4965_ID_BROADCAST 31
+
+ uint8_t flags;
+#define IWN_FLAG_SET_KEY (1 << 0)
+#define IWN_FLAG_SET_DISABLE_TID (1 << 1)
+#define IWN_FLAG_SET_TXRATE (1 << 2)
+#define IWN_FLAG_SET_ADDBA (1 << 3)
+#define IWN_FLAG_SET_DELBA (1 << 4)
+
+ uint16_t reserved3;
+ uint16_t kflags;
+#define IWN_KFLAG_CCMP (1 << 1)
+#define IWN_KFLAG_MAP (1 << 3)
+#define IWN_KFLAG_KID(kid) ((kid) << 8)
+#define IWN_KFLAG_INVALID (1 << 11)
+#define IWN_KFLAG_GROUP (1 << 14)
+
+ uint8_t tsc2; /* TKIP TSC2 */
+ uint8_t reserved4;
+ uint16_t ttak[5];
+ uint8_t kid;
+ uint8_t reserved5;
+ uint8_t key[16];
+ /* The following 3 fields are for 5000 Series only. */
+ uint64_t tsc;
+ uint8_t rxmic[IEEE80211_TKIP_MICLEN];
+ uint8_t txmic[IEEE80211_TKIP_MICLEN];
+
+ uint32_t htflags;
+#define IWN_AMDPU_SIZE_FACTOR(x) ((x) << 19)
+#define IWN_AMDPU_DENSITY(x) ((x) << 23)
+
+ uint32_t mask;
+ uint16_t disable_tid;
+ uint16_t reserved6;
+ uint8_t addba_tid;
+ uint8_t delba_tid;
+ uint16_t addba_ssn;
+ uint32_t reserved7;
+} __packed;
+
+struct iwn4965_node_info {
+ uint8_t control;
+ uint8_t reserved1[3];
+ uint8_t macaddr[IEEE80211_ADDR_LEN];
+ uint16_t reserved2;
+ uint8_t id;
+ uint8_t flags;
+ uint16_t reserved3;
+ uint16_t kflags;
+ uint8_t tsc2; /* TKIP TSC2 */
+ uint8_t reserved4;
+ uint16_t ttak[5];
+ uint8_t kid;
+ uint8_t reserved5;
+ uint8_t key[16];
+ uint32_t htflags;
+ uint32_t mask;
+ uint16_t disable_tid;
+ uint16_t reserved6;
+ uint8_t addba_tid;
+ uint8_t delba_tid;
+ uint16_t addba_ssn;
+ uint32_t reserved7;
+} __packed;
+
+#define IWN_RFLAG_CCK (1 << 1)
+#define IWN_RFLAG_ANT(x) ((x) << 6)
+
+/* Structure for command IWN_CMD_TX_DATA. */
+struct iwn_cmd_data {
+ uint16_t len;
+ uint16_t lnext;
+ uint32_t flags;
+#define IWN_TX_NEED_PROTECTION (1 << 0) /* 5000 only */
+#define IWN_TX_NEED_RTS (1 << 1)
+#define IWN_TX_NEED_CTS (1 << 2)
+#define IWN_TX_NEED_ACK (1 << 3)
+#define IWN_TX_LINKQ (1 << 4)
+#define IWN_TX_IMM_BA (1 << 6)
+#define IWN_TX_FULL_TXOP (1 << 7)
+#define IWN_TX_BT_DISABLE (1 << 12) /* bluetooth coexistence */
+#define IWN_TX_AUTO_SEQ (1 << 13)
+#define IWN_TX_MORE_FRAG (1 << 14)
+#define IWN_TX_INSERT_TSTAMP (1 << 16)
+#define IWN_TX_NEED_PADDING (1 << 20)
+
+ uint32_t scratch;
+ uint8_t plcp;
+ uint8_t rflags;
+ uint16_t xrflags;
+
+ uint8_t id;
+ uint8_t security;
+#define IWN_CIPHER_WEP40 1
+#define IWN_CIPHER_CCMP 2
+#define IWN_CIPHER_TKIP 3
+#define IWN_CIPHER_WEP104 9
+
+ uint8_t linkq;
+ uint8_t reserved2;
+ uint8_t key[16];
+ uint16_t fnext;
+ uint16_t reserved3;
+ uint32_t lifetime;
+#define IWN_LIFETIME_INFINITE 0xffffffff
+
+ uint32_t loaddr;
+ uint8_t hiaddr;
+ uint8_t rts_ntries;
+ uint8_t data_ntries;
+ uint8_t tid;
+ uint16_t timeout;
+ uint16_t txop;
+} __packed;
+
+/* Structure for command IWN_CMD_LINK_QUALITY. */
+#define IWN_MAX_TX_RETRIES 16
+struct iwn_cmd_link_quality {
+ uint8_t id;
+ uint8_t reserved1;
+ uint16_t ctl;
+ uint8_t flags;
+ uint8_t mimo;
+ uint8_t antmsk_1stream;
+ uint8_t antmsk_2stream;
+ uint8_t ridx[WME_NUM_AC];
+ uint16_t ampdu_limit;
+ uint8_t ampdu_threshold;
+ uint8_t ampdu_max;
+ uint32_t reserved2;
+ struct {
+ uint8_t plcp;
+ uint8_t rflags;
+ uint16_t xrflags;
+ } __packed retry[IWN_MAX_TX_RETRIES];
+ uint32_t reserved3;
+} __packed;
+
+/* Structure for command IWN_CMD_SET_LED. */
+struct iwn_cmd_led {
+ uint32_t unit; /* multiplier (in usecs) */
+ uint8_t which;
+#define IWN_LED_ACTIVITY 1
+#define IWN_LED_LINK 2
+
+ uint8_t off;
+ uint8_t on;
+ uint8_t reserved;
+} __packed;
+
+/* Structure for command IWN5000_CMD_WIMAX_COEX. */
+struct iwn5000_wimax_coex {
+ uint32_t flags;
+#define IWN_WIMAX_COEX_STA_TABLE_VALID (1 << 0)
+#define IWN_WIMAX_COEX_UNASSOC_WA_UNMASK (1 << 2)
+#define IWN_WIMAX_COEX_ASSOC_WA_UNMASK (1 << 3)
+#define IWN_WIMAX_COEX_ENABLE (1 << 7)
+
+ struct iwn5000_wimax_event {
+ uint8_t request;
+ uint8_t window;
+ uint8_t reserved;
+ uint8_t flags;
+ } __packed events[16];
+} __packed;
+
+/* Structures for command IWN5000_CMD_CALIB_CONFIG. */
+struct iwn5000_calib_elem {
+ uint32_t enable;
+ uint32_t start;
+#define IWN5000_CALIB_DC (1 << 1)
+ uint32_t send;
+ uint32_t apply;
+ uint32_t reserved;
+} __packed;
+
+struct iwn5000_calib_status {
+ struct iwn5000_calib_elem once;
+ struct iwn5000_calib_elem perd;
+ uint32_t flags;
+} __packed;
+
+struct iwn5000_calib_config {
+ struct iwn5000_calib_status ucode;
+ struct iwn5000_calib_status driver;
+ uint32_t reserved;
+} __packed;
+
+/* Structure for command IWN_CMD_SET_POWER_MODE. */
+struct iwn_pmgt_cmd {
+ uint16_t flags;
+#define IWN_PS_ALLOW_SLEEP (1 << 0)
+#define IWN_PS_NOTIFY (1 << 1)
+#define IWN_PS_SLEEP_OVER_DTIM (1 << 2)
+#define IWN_PS_PCI_PMGT (1 << 3)
+#define IWN_PS_FAST_PD (1 << 4)
+
+ uint8_t keepalive;
+ uint8_t debug;
+ uint32_t rxtimeout;
+ uint32_t txtimeout;
+ uint32_t intval[5];
+ uint32_t beacons;
+} __packed;
+
+/* Structures for command IWN_CMD_SCAN. */
+struct iwn_scan_essid {
+ uint8_t id;
+ uint8_t len;
+ uint8_t data[IEEE80211_NWID_LEN];
+} __packed;
+
+struct iwn_scan_hdr {
+ uint16_t len;
+ uint8_t scan_flags;
+#define IWN_SCAN_PASSIVE2ACTIVE (1<<5)
+
+ uint8_t nchan;
+ uint16_t quiet_time;
+ uint16_t quiet_threshold;
+ uint16_t crc_threshold;
+ uint16_t rxchain;
+ uint32_t max_svc; /* background scans */
+ uint32_t pause_svc; /* background scans */
+ uint32_t flags;
+ uint32_t filter;
+
+ /* Followed by a struct iwn_cmd_data. */
+ /* Followed by an array of 20 structs iwn_scan_essid. */
+ /* Followed by probe request body. */
+ /* Followed by an array of ``nchan'' structs iwn_scan_chan. */
+} __packed;
+
+struct iwn_scan_chan {
+ uint32_t flags;
+#define IWN_CHAN_ACTIVE (1 << 0)
+#define IWN_CHAN_NPBREQS(x) (((1 << (x)) - 1) << 1)
+
+ uint16_t chan;
+ uint8_t rf_gain;
+ uint8_t dsp_gain;
+ uint16_t active; /* msecs */
+ uint16_t passive; /* msecs */
+} __packed;
+
+/* Maximum size of a scan command. */
+#define IWN_SCAN_MAXSZ 4092
+
+/*
+ * For active scan, listen ACTIVE_DWELL_TIME (msec) on each channel after
+ * sending probe req. This should be set long enough to hear probe responses
+ * from more than one AP.
+ */
+#define IWN_ACTIVE_DWELL_TIME_2GHZ (30) /* all times in msec */
+#define IWN_ACTIVE_DWELL_TIME_5GHZ (20)
+#define IWN_ACTIVE_DWELL_FACTOR_2GHZ (3)
+#define IWN_ACTIVE_DWELL_FACTOR_5GHZ (2)
+
+/*
+ * For passive scan, listen PASSIVE_DWELL_TIME (msec) on each channel.
+ * Must be set longer than active dwell time.
+ * For the most reliable scan, set > AP beacon interval (typically 100msec).
+ */
+#define IWN_PASSIVE_DWELL_TIME_2GHZ (20) /* all times in msec */
+#define IWN_PASSIVE_DWELL_TIME_5GHZ (10)
+#define IWN_PASSIVE_DWELL_BASE (100)
+#define IWN_CHANNEL_TUNE_TIME (5)
+
+/*
+ * If active scanning is requested but a certain channel is
+ * marked passive, we can do active scanning if we detect
+ * transmissions.
+ *
+ * There is an issue with some firmware versions that triggers
+ * a sysassert on a "good CRC threshold" of zero (== disabled),
+ * on a radar channel even though this means that we should NOT
+ * send probes.
+ *
+ * The "good CRC threshold" is the number of frames that we
+ * need to receive during our dwell time on a channel before
+ * sending out probes -- setting this to a huge value will
+ * mean we never reach it, but at the same time work around
+ * the aforementioned issue. Thus use IWN_GOOD_CRC_TH_NEVER
+ * here instead of IWN_GOOD_CRC_TH_DISABLED.
+ *
+ * This was fixed in later versions along with some other
+ * scan changes, and the threshold behaves as a flag in those
+ * versions.
+ */
+#define IWN_GOOD_CRC_TH_DISABLED 0
+#define IWN_GOOD_CRC_TH_DEFAULT htole16(1)
+#define IWN_GOOD_CRC_TH_NEVER htole16(0xffff)
+
+/* Structure for command IWN_CMD_TXPOWER (4965AGN only.) */
+#define IWN_RIDX_MAX 32
+struct iwn4965_cmd_txpower {
+ uint8_t band;
+ uint8_t reserved1;
+ uint8_t chan;
+ uint8_t reserved2;
+ struct {
+ uint8_t rf_gain[2];
+ uint8_t dsp_gain[2];
+ } __packed power[IWN_RIDX_MAX + 1];
+} __packed;
+
+/* Structure for command IWN_CMD_TXPOWER_DBM (5000 Series only.) */
+struct iwn5000_cmd_txpower {
+ int8_t global_limit; /* in half-dBm */
+#define IWN5000_TXPOWER_AUTO 0x7f
+#define IWN5000_TXPOWER_MAX_DBM 16
+
+ uint8_t flags;
+#define IWN5000_TXPOWER_NO_CLOSED (1 << 6)
+
+ int8_t srv_limit; /* in half-dBm */
+ uint8_t reserved;
+} __packed;
+
+/* Structure for command IWN_CMD_BT_COEX. */
+struct iwn_bluetooth {
+ uint8_t flags;
+#define IWN_BT_COEX_CHAN_ANN (1 << 0)
+#define IWN_BT_COEX_BT_PRIO (1 << 1)
+#define IWN_BT_COEX_2_WIRE (1 << 2)
+#define IWN_BT_COEX_ENABLE (IWN_BT_COEX_CHAN_ANN | IWN_BT_COEX_BT_PRIO)
+ uint8_t lead_time;
+#define IWN_BT_LEAD_TIME_DEF 30
+ uint8_t max_kill;
+#define IWN_BT_MAX_KILL_DEF 5
+ uint8_t bt3_timer_t7_value;
+#define IWN_BT_BT3_T7_DEF 1
+ uint32_t kill_ack_mask;
+#define IWN_BT_KILL_ACK_MASK_DEF htole32(0xffff0000)
+ uint32_t kill_cts_mask;
+#define IWN_BT_KILL_CTS_MASK_DEF htole32(0xffff0000)
+} __packed;
+
+struct iwn_bt_basic {
+ struct iwn_bluetooth bt;
+#define IWN_BT_BASIC_CHAN_INHIBITION 1
+#define IWN_BT_BASIC_MODE_MASK ((1 << 3) | (1 << 4) | (1 << 5))
+#define IWN_BT_BASIC_MODE_SHIFT 3
+#define IWN_BT_BASIC_MODE_DISABLED 0
+#define IWN_BT_BASIC_MODE_LEGACY_2W 1
+#define IWN_BT_BASIC_MODE_3W 2
+#define IWN_BT_BASIC_MODE_4W 3
+#define IWN_BT_UCODE_DEFAULT (1 << 6)
+#define IWN_BT_SYNC_2_BT_DISABLE (1 << 7)
+ uint8_t bt3_prio_sample_time;
+#define IWN_BT_BT3_PRIO_SAMPLE_DEF 2
+ uint8_t bt3_timer_t2_value;
+#define IWN_BT_BT3_T2_DEF 12
+ uint16_t bt4_reaction_time; /* unused */
+ uint32_t bt3_lookup_table[12];
+
+ uint16_t reduce_txpower; /* bit 0 */
+#if 0
+ /*
+ * The original code causes problems with lint. These declarations
+ * could be fixed with lint tags, but the assignment to
+ * reduce_txpower in iwn_config_bt_coex_adv_config() cannot.
+ * For reference it remains here but is ifdef'ed out.
+ */
+ union {
+ struct {
+ uint8_t reduce_txpower; /* bit 0 */
+ uint8_t reserved;
+ };
+ uint16_t bt4_decision;
+ };
+#endif
+ uint16_t valid;
+#define IWN_BT_VALID_ENABLE_FLAGS htole16(1 << 0)
+#define IWN_BT_VALID_BOOST htole16(1 << 1)
+#define IWN_BT_VALID_MAX_KILL htole16(1 << 2)
+#define IWN_BT_VALID_3W_TIMERS htole16(1 << 3)
+#define IWN_BT_VALID_KILL_ACK_MASK htole16(1 << 4)
+#define IWN_BT_VALID_KILL_CTS_MASK htole16(1 << 5)
+#define IWN_BT_VALID_REDUCED_TX_PWR htole16(1 << 6)
+#define IWN_BT_VALID_3W_LUT htole16(1 << 7)
+#define IWN_BT_ALL_VALID_MASK (IWN_BT_VALID_ENABLE_FLAGS | \
+ IWN_BT_VALID_BOOST | \
+ IWN_BT_VALID_MAX_KILL | \
+ IWN_BT_VALID_3W_TIMERS | \
+ IWN_BT_VALID_KILL_ACK_MASK | \
+ IWN_BT_VALID_KILL_CTS_MASK | \
+ IWN_BT_VALID_REDUCED_TX_PWR | \
+ IWN_BT_VALID_3W_LUT)
+} __packed;
+
+struct iwn_bt_adv1 {
+ struct iwn_bt_basic basic;
+ uint8_t prio_boost;
+#define IWN_BT_PRIO_BOOST_DEF 0xf0
+ /* set IWLAGN_BT_VALID_BOOST to "1" in "valid" bitmask for */
+ uint8_t tx_prio_boost;
+ uint16_t rx_prio_boost;
+} __packed;
+
+struct iwn_bt_adv2 {
+ struct iwn_bt_basic basic;
+ uint32_t prio_boost;
+#define IWN_BT_PRIO_BOOST_DEF32 0xf0f0f0
+ uint8_t reserved;
+ /* set IWLAGN_BT_VALID_BOOST to "1" in "valid" bitmask for */
+ uint8_t tx_prio_boost;
+ uint16_t rx_prio_boost;
+} __packed;
+
+/* Structure for command IWN_CMD_BT_COEX_PRIOTABLE */
+struct iwn_btcoex_priotable {
+ uint8_t calib_init1;
+ uint8_t calib_init2;
+ uint8_t calib_periodic_low1;
+ uint8_t calib_periodic_low2;
+ uint8_t calib_periodic_high1;
+ uint8_t calib_periodic_high2;
+ uint8_t dtim;
+ uint8_t scan52;
+ uint8_t scan24;
+ uint8_t reserved[7];
+} __packed;
+
+/* Structure for command IWN_CMD_BT_COEX_PROT */
+struct iwn_btcoex_prot {
+ uint8_t open;
+ uint8_t type;
+ uint8_t reserved[2];
+} __packed;
+
+/* Structure for command IWN_CMD_SET_CRITICAL_TEMP. */
+struct iwn_critical_temp {
+ uint32_t reserved;
+ uint32_t tempM;
+ uint32_t tempR;
+/* degK <-> degC conversion macros. */
+#define IWN_CTOK(c) ((c) + 273)
+#define IWN_KTOC(k) ((k) - 273)
+#define IWN_CTOMUK(c) (((c) * 1000000) + 273150000)
+} __packed;
+
+/* Structures for command IWN_CMD_SET_SENSITIVITY. */
+struct iwn_sensitivity_cmd {
+ uint16_t which;
+#define IWN_SENSITIVITY_DEFAULTTBL 0
+#define IWN_SENSITIVITY_WORKTBL 1
+
+ uint16_t energy_cck;
+ uint16_t energy_ofdm;
+ uint16_t corr_ofdm_x1;
+ uint16_t corr_ofdm_mrc_x1;
+ uint16_t corr_cck_mrc_x4;
+ uint16_t corr_ofdm_x4;
+ uint16_t corr_ofdm_mrc_x4;
+ uint16_t corr_barker;
+ uint16_t corr_barker_mrc;
+ uint16_t corr_cck_x4;
+ uint16_t energy_ofdm_th;
+} __packed;
+
+struct iwn_enhanced_sensitivity_cmd {
+ uint16_t which;
+ uint16_t energy_cck;
+ uint16_t energy_ofdm;
+ uint16_t corr_ofdm_x1;
+ uint16_t corr_ofdm_mrc_x1;
+ uint16_t corr_cck_mrc_x4;
+ uint16_t corr_ofdm_x4;
+ uint16_t corr_ofdm_mrc_x4;
+ uint16_t corr_barker;
+ uint16_t corr_barker_mrc;
+ uint16_t corr_cck_x4;
+ uint16_t energy_ofdm_th;
+ /* "Enhanced" part. */
+ uint16_t ina_det_ofdm;
+ uint16_t ina_det_cck;
+ uint16_t corr_11_9_en;
+ uint16_t ofdm_det_slope_mrc;
+ uint16_t ofdm_det_icept_mrc;
+ uint16_t ofdm_det_slope;
+ uint16_t ofdm_det_icept;
+ uint16_t cck_det_slope_mrc;
+ uint16_t cck_det_icept_mrc;
+ uint16_t cck_det_slope;
+ uint16_t cck_det_icept;
+ uint16_t reserved;
+} __packed;
+
+/* Structures for command IWN_CMD_PHY_CALIB. */
+struct iwn_phy_calib {
+ uint8_t code;
+#define IWN4965_PHY_CALIB_DIFF_GAIN 7
+#define IWN5000_PHY_CALIB_DC 8
+#define IWN5000_PHY_CALIB_LO 9
+#define IWN5000_PHY_CALIB_TX_IQ 11
+#define IWN5000_PHY_CALIB_CRYSTAL 15
+#define IWN5000_PHY_CALIB_BASE_BAND 16
+#define IWN5000_PHY_CALIB_TX_IQ_PERIODIC 17
+#define IWN5000_PHY_CALIB_RESET_NOISE_GAIN 18
+#define IWN5000_PHY_CALIB_NOISE_GAIN 19
+
+#define IWN6000_PHY_CALIB_TEMP_OFFSET 18
+#define IWN2000_PHY_CALIB_TEMP_OFFSET 18
+
+#define IWN5000_PHY_CALIB_MAX 253
+
+ uint8_t group;
+ uint8_t ngroups;
+ uint8_t isvalid;
+} __packed;
+
+struct iwn5000_phy_calib_crystal {
+ uint8_t code;
+ uint8_t group;
+ uint8_t ngroups;
+ uint8_t isvalid;
+
+ uint8_t cap_pin[2];
+ uint8_t reserved[2];
+} __packed;
+
+struct iwn6000_phy_calib_temp_offset {
+ uint8_t code;
+ uint8_t group;
+ uint8_t ngroups;
+ uint8_t isvalid;
+ int16_t offset;
+#define IWN_DEFAULT_TEMP_OFFSET 2700
+
+ uint16_t reserved;
+} __packed;
+
+struct iwn2000_phy_calib_temp_offset {
+ uint8_t code;
+ uint8_t group;
+ uint8_t ngroups;
+ uint8_t isvalid;
+ int16_t offset_high;
+ int16_t offset_low;
+ int16_t burnt_voltage_ref;
+ int16_t reserved;
+} __packed;
+
+struct iwn_phy_calib_gain {
+ uint8_t code;
+ uint8_t group;
+ uint8_t ngroups;
+ uint8_t isvalid;
+
+ int8_t gain[3];
+ uint8_t reserved;
+} __packed;
+
+/* Structure for command IWN_CMD_SPECTRUM_MEASUREMENT. */
+struct iwn_spectrum_cmd {
+ uint16_t len;
+ uint8_t token;
+ uint8_t id;
+ uint8_t origin;
+ uint8_t periodic;
+ uint16_t timeout;
+ uint32_t start;
+ uint32_t reserved1;
+ uint32_t flags;
+ uint32_t filter;
+ uint16_t nchan;
+ uint16_t reserved2;
+ struct {
+ uint32_t duration;
+ uint8_t chan;
+ uint8_t type;
+#define IWN_MEASUREMENT_BASIC (1 << 0)
+#define IWN_MEASUREMENT_CCA (1 << 1)
+#define IWN_MEASUREMENT_RPI_HISTOGRAM (1 << 2)
+#define IWN_MEASUREMENT_NOISE_HISTOGRAM (1 << 3)
+#define IWN_MEASUREMENT_FRAME (1 << 4)
+#define IWN_MEASUREMENT_IDLE (1 << 7)
+
+ uint16_t reserved;
+ } __packed chan[10];
+} __packed;
+
+/* Structure for IWN_UC_READY notification. */
+#define IWN_NATTEN_GROUPS 5
+struct iwn_ucode_info {
+ uint8_t minor;
+ uint8_t major;
+ uint16_t reserved1;
+ uint8_t revision[8];
+ uint8_t type;
+ uint8_t subtype;
+#define IWN_UCODE_RUNTIME 0
+#define IWN_UCODE_INIT 9
+
+ uint16_t reserved2;
+ uint32_t logptr;
+ uint32_t errptr;
+ uint32_t tstamp;
+ uint32_t valid;
+
+ /* The following fields are for UCODE_INIT only. */
+ int32_t volt;
+ struct {
+ int32_t chan20MHz;
+ int32_t chan40MHz;
+ } __packed temp[4];
+ int32_t atten[IWN_NATTEN_GROUPS][2];
+} __packed;
+
+/* Structures for IWN_TX_DONE notification. */
+struct iwn4965_tx_stat {
+ uint8_t nframes;
+ uint8_t btkillcnt;
+ uint8_t rtsfailcnt;
+ uint8_t ackfailcnt;
+ uint8_t rate;
+ uint8_t rflags;
+ uint16_t xrflags;
+ uint16_t duration;
+ uint16_t reserved;
+ uint32_t power[2];
+ uint32_t status;
+} __packed;
+
+struct iwn5000_tx_stat {
+ uint8_t nframes;
+ uint8_t btkillcnt;
+ uint8_t rtsfailcnt;
+ uint8_t ackfailcnt;
+ uint8_t rate;
+ uint8_t rflags;
+ uint16_t xrflags;
+ uint16_t duration;
+ uint16_t reserved;
+ uint32_t power[2];
+ uint32_t info;
+ uint16_t seq;
+ uint16_t len;
+ uint8_t tlc;
+ uint8_t ratid;
+ uint8_t fc[2];
+ uint16_t status;
+ uint16_t sequence;
+} __packed;
+
+/* Structure for IWN_BEACON_MISSED notification. */
+struct iwn_beacon_missed {
+ uint32_t consecutive;
+ uint32_t total;
+ uint32_t expected;
+ uint32_t received;
+} __packed;
+
+/* Structure for IWN_MPDU_RX_DONE notification. */
+struct iwn_rx_mpdu {
+ uint16_t len;
+ uint16_t reserved;
+} __packed;
+
+/* Structures for IWN_RX_DONE and IWN_MPDU_RX_DONE notifications. */
+struct iwn4965_rx_phystat {
+ uint16_t antenna;
+ uint16_t agc;
+ uint8_t rssi[6];
+} __packed;
+
+struct iwn5000_rx_phystat {
+ uint32_t reserved1;
+ uint32_t agc;
+ uint16_t rssi[3];
+} __packed;
+
+struct iwn_rx_stat {
+ uint8_t phy_len;
+ uint8_t cfg_phy_len;
+#define IWN_STAT_MAXLEN 20
+
+ uint8_t id;
+ uint8_t reserved1;
+ uint64_t tstamp;
+ uint32_t beacon;
+ uint16_t flags;
+#define IWN_STAT_FLAG_SHPREAMBLE (1 << 2)
+
+ uint16_t chan;
+ uint8_t phybuf[32];
+ uint8_t rate;
+ uint8_t rflags;
+ uint16_t xrflags;
+ uint16_t len;
+ uint16_t reserve3;
+} __packed;
+
+#define IWN_RSSI_TO_DBM 44
+
+/* Structure for IWN_RX_COMPRESSED_BA notification. */
+struct iwn_compressed_ba {
+ uint8_t macaddr[IEEE80211_ADDR_LEN];
+ uint16_t reserved;
+ uint8_t id;
+ uint8_t tid;
+ uint16_t seq;
+ uint64_t bitmap;
+ uint16_t qid;
+ uint16_t ssn;
+} __packed;
+
+/* Structure for IWN_START_SCAN notification. */
+struct iwn_start_scan {
+ uint64_t tstamp;
+ uint32_t tbeacon;
+ uint8_t chan;
+ uint8_t band;
+ uint16_t reserved;
+ uint32_t status;
+} __packed;
+
+/* Structure for IWN_STOP_SCAN notification. */
+struct iwn_stop_scan {
+ uint8_t nchan;
+ uint8_t status;
+ uint8_t reserved;
+ uint8_t chan;
+ uint64_t tsf;
+} __packed;
+
+/* Structure for IWN_SPECTRUM_MEASUREMENT notification. */
+struct iwn_spectrum_notif {
+ uint8_t id;
+ uint8_t token;
+ uint8_t idx;
+ uint8_t state;
+#define IWN_MEASUREMENT_START 0
+#define IWN_MEASUREMENT_STOP 1
+
+ uint32_t start;
+ uint8_t band;
+ uint8_t chan;
+ uint8_t type;
+ uint8_t reserved1;
+ uint32_t cca_ofdm;
+ uint32_t cca_cck;
+ uint32_t cca_time;
+ uint8_t basic;
+ uint8_t reserved2[3];
+ uint32_t ofdm[8];
+ uint32_t cck[8];
+ uint32_t stop;
+ uint32_t status;
+#define IWN_MEASUREMENT_OK 0
+#define IWN_MEASUREMENT_CONCURRENT 1
+#define IWN_MEASUREMENT_CSA_CONFLICT 2
+#define IWN_MEASUREMENT_TGH_CONFLICT 3
+#define IWN_MEASUREMENT_STOPPED 6
+#define IWN_MEASUREMENT_TIMEOUT 7
+#define IWN_MEASUREMENT_FAILED 8
+} __packed;
+
+/* Structures for IWN_{RX,BEACON}_STATISTICS notification. */
+struct iwn_rx_phy_stats {
+ uint32_t ina;
+ uint32_t fina;
+ uint32_t bad_plcp;
+ uint32_t bad_crc32;
+ uint32_t overrun;
+ uint32_t eoverrun;
+ uint32_t good_crc32;
+ uint32_t fa;
+ uint32_t bad_fina_sync;
+ uint32_t sfd_timeout;
+ uint32_t fina_timeout;
+ uint32_t no_rts_ack;
+ uint32_t rxe_limit;
+ uint32_t ack;
+ uint32_t cts;
+ uint32_t ba_resp;
+ uint32_t dsp_kill;
+ uint32_t bad_mh;
+ uint32_t rssi_sum;
+ uint32_t reserved;
+} __packed;
+
+struct iwn_rx_general_stats {
+ uint32_t bad_cts;
+ uint32_t bad_ack;
+ uint32_t not_bss;
+ uint32_t filtered;
+ uint32_t bad_chan;
+ uint32_t beacons;
+ uint32_t missed_beacons;
+ uint32_t adc_saturated; /* time in 0.8us */
+ uint32_t ina_searched; /* time in 0.8us */
+ int32_t noise[3];
+ uint32_t flags;
+ uint32_t load;
+ uint32_t fa;
+ uint32_t rssi[3];
+ uint32_t energy[3];
+} __packed;
+
+struct iwn_rx_ht_phy_stats {
+ uint32_t bad_plcp;
+ uint32_t overrun;
+ uint32_t eoverrun;
+ uint32_t good_crc32;
+ uint32_t bad_crc32;
+ uint32_t bad_mh;
+ uint32_t good_ampdu_crc32;
+ uint32_t ampdu;
+ uint32_t fragment;
+ uint32_t reserved;
+} __packed;
+
+struct iwn_rx_stats {
+ struct iwn_rx_phy_stats ofdm;
+ struct iwn_rx_phy_stats cck;
+ struct iwn_rx_general_stats general;
+ struct iwn_rx_ht_phy_stats ht;
+} __packed;
+
+struct iwn_tx_stats {
+ uint32_t preamble;
+ uint32_t rx_detected;
+ uint32_t bt_defer;
+ uint32_t bt_kill;
+ uint32_t short_len;
+ uint32_t cts_timeout;
+ uint32_t ack_timeout;
+ uint32_t exp_ack;
+ uint32_t ack;
+ uint32_t msdu;
+ uint32_t busrt_err1;
+ uint32_t burst_err2;
+ uint32_t cts_collision;
+ uint32_t ack_collision;
+ uint32_t ba_timeout;
+ uint32_t ba_resched;
+ uint32_t query_ampdu;
+ uint32_t query;
+ uint32_t query_ampdu_frag;
+ uint32_t query_mismatch;
+ uint32_t not_ready;
+ uint32_t underrun;
+ uint32_t bt_ht_kill;
+ uint32_t rx_ba_resp;
+ uint32_t reserved[2];
+} __packed;
+
+struct iwn_general_stats {
+ uint32_t temp;
+ uint32_t temp_m;
+ uint32_t burst_check;
+ uint32_t burst;
+ uint32_t reserved1[4];
+ uint32_t sleep;
+ uint32_t slot_out;
+ uint32_t slot_idle;
+ uint32_t ttl_tstamp;
+ uint32_t tx_ant_a;
+ uint32_t tx_ant_b;
+ uint32_t exec;
+ uint32_t probe;
+ uint32_t reserved2[2];
+ uint32_t rx_enabled;
+ uint32_t reserved3[3];
+} __packed;
+
+struct iwn_stats {
+ uint32_t flags;
+ struct iwn_rx_stats rx;
+ struct iwn_tx_stats tx;
+ struct iwn_general_stats general;
+} __packed;
+
+
+/* Firmware error dump. */
+struct iwn_fw_dump {
+ uint32_t valid;
+ uint32_t id;
+ uint32_t pc;
+ uint32_t branch_link[2];
+ uint32_t interrupt_link[2];
+ uint32_t error_data[2];
+ uint32_t src_line;
+ uint32_t tsf;
+ uint32_t time[2];
+} __packed;
+
+/* TLV firmware header. */
+struct iwn_fw_tlv_hdr {
+ uint32_t zero; /* Always 0, to differentiate from legacy. */
+ uint32_t signature;
+#define IWN_FW_SIGNATURE 0x0a4c5749 /* "IWL\n" */
+
+ uint8_t descr[64];
+ uint32_t rev;
+#define IWN_FW_API(x) (((x) >> 8) & 0xff)
+
+ uint32_t build;
+ uint64_t altmask;
+} __packed;
+
+/* TLV header. */
+struct iwn_fw_tlv {
+ uint16_t type;
+#define IWN_FW_TLV_MAIN_TEXT 1
+#define IWN_FW_TLV_MAIN_DATA 2
+#define IWN_FW_TLV_INIT_TEXT 3
+#define IWN_FW_TLV_INIT_DATA 4
+#define IWN_FW_TLV_BOOT_TEXT 5
+#define IWN_FW_TLV_PBREQ_MAXLEN 6
+#define IWN_FW_TLV_ENH_SENS 14
+#define IWN_FW_TLV_PHY_CALIB 15
+#define IWN_FW_TLV_FLAGS 18
+
+ uint16_t alt;
+ uint32_t len;
+} __packed;
+
+#define IWN4965_FW_TEXT_MAXSZ ( 96 * 1024)
+#define IWN4965_FW_DATA_MAXSZ ( 40 * 1024)
+#define IWN5000_FW_TEXT_MAXSZ (256 * 1024)
+#define IWN5000_FW_DATA_MAXSZ ( 80 * 1024)
+#define IWN_FW_BOOT_TEXT_MAXSZ 1024
+#define IWN4965_FWSZ (IWN4965_FW_TEXT_MAXSZ + IWN4965_FW_DATA_MAXSZ)
+#define IWN5000_FWSZ IWN5000_FW_TEXT_MAXSZ
+
+/**
+ * enum iwn_ucode_tlv_flag - ucode API flags
+ * @IWN_UCODE_TLV_FLAGS_PAN: This is PAN capable microcode; this previously
+ * was a separate TLV but moved here to save space.
+ * @IWN_UCODE_TLV_FLAGS_NEWSCAN: new uCode scan behaviour on hidden SSID,
+ * treats good CRC threshold as a boolean
+ * @IWN_UCODE_TLV_FLAGS_MFP: This uCode image supports MFP (802.11w).
+ * @IWN_UCODE_TLV_FLAGS_P2P: This uCode image supports P2P.
+ * @IWN_UCODE_TLV_FLAGS_DW_BC_TABLE: The SCD byte count table is in DWORDS
+ * @IWN_UCODE_TLV_FLAGS_UAPSD: This uCode image supports uAPSD
+ * @IWN_UCODE_TLV_FLAGS_SHORT_BL: 16 entries of black list instead of 64 in scan
+ * offload profile config command.
+ * @IWN_UCODE_TLV_FLAGS_RX_ENERGY_API: supports rx signal strength api
+ * @IWN_UCODE_TLV_FLAGS_TIME_EVENT_API_V2: using the new time event API.
+ * @IWN_UCODE_TLV_FLAGS_D3_6_IPV6_ADDRS: D3 image supports up to six
+ * (rather than two) IPv6 addresses
+ * @IWN_UCODE_TLV_FLAGS_BF_UPDATED: new beacon filtering API
+ * @IWN_UCODE_TLV_FLAGS_NO_BASIC_SSID: not sending a probe with the SSID element
+ * from the probe request template.
+ * @IWN_UCODE_TLV_FLAGS_D3_CONTINUITY_API: modified D3 API to allow keeping
+ * connection when going back to D0
+ * @IWN_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL: new NS offload (small version)
+ * @IWN_UCODE_TLV_FLAGS_NEW_NSOFFL_LARGE: new NS offload (large version)
+ * @IWN_UCODE_TLV_FLAGS_SCHED_SCAN: this uCode image supports scheduled scan.
+ * @IWN_UCODE_TLV_FLAGS_STA_KEY_CMD: new ADD_STA and ADD_STA_KEY command API
+ * @IWN_UCODE_TLV_FLAGS_DEVICE_PS_CMD: support device wide power command
+ * containing CAM (Continuous Active Mode) indication.
+ */
+enum iwn_ucode_tlv_flag {
+ IWN_UCODE_TLV_FLAGS_PAN = (1 << 0),
+ IWN_UCODE_TLV_FLAGS_NEWSCAN = (1 << 1),
+ IWN_UCODE_TLV_FLAGS_MFP = (1 << 2),
+ IWN_UCODE_TLV_FLAGS_P2P = (1 << 3),
+ IWN_UCODE_TLV_FLAGS_DW_BC_TABLE = (1 << 4),
+ IWN_UCODE_TLV_FLAGS_NEWBT_COEX = (1 << 5),
+ IWN_UCODE_TLV_FLAGS_UAPSD = (1 << 6),
+ IWN_UCODE_TLV_FLAGS_SHORT_BL = (1 << 7),
+ IWN_UCODE_TLV_FLAGS_RX_ENERGY_API = (1 << 8),
+ IWN_UCODE_TLV_FLAGS_TIME_EVENT_API_V2 = (1 << 9),
+ IWN_UCODE_TLV_FLAGS_D3_6_IPV6_ADDRS = (1 << 10),
+ IWN_UCODE_TLV_FLAGS_BF_UPDATED = (1 << 11),
+ IWN_UCODE_TLV_FLAGS_NO_BASIC_SSID = (1 << 12),
+ IWN_UCODE_TLV_FLAGS_D3_CONTINUITY_API = (1 << 14),
+ IWN_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL = (1 << 15),
+ IWN_UCODE_TLV_FLAGS_NEW_NSOFFL_LARGE = (1 << 16),
+ IWN_UCODE_TLV_FLAGS_SCHED_SCAN = (1 << 17),
+ IWN_UCODE_TLV_FLAGS_STA_KEY_CMD = (1 << 19),
+ IWN_UCODE_TLV_FLAGS_DEVICE_PS_CMD = (1 << 20),
+};
+
+/*
+ * Offsets into EEPROM.
+ */
+#define IWN_EEPROM_MAC 0x015
+#define IWN_EEPROM_SKU_CAP 0x045
+#define IWN_EEPROM_RFCFG 0x048
+#define IWN4965_EEPROM_DOMAIN 0x060
+#define IWN4965_EEPROM_BAND1 0x063
+#define IWN5000_EEPROM_REG 0x066
+#define IWN5000_EEPROM_CAL 0x067
+#define IWN4965_EEPROM_BAND2 0x072
+#define IWN4965_EEPROM_BAND3 0x080
+#define IWN4965_EEPROM_BAND4 0x08d
+#define IWN4965_EEPROM_BAND5 0x099
+#define IWN4965_EEPROM_BAND6 0x0a0
+#define IWN4965_EEPROM_BAND7 0x0a8
+#define IWN4965_EEPROM_MAXPOW 0x0e8
+#define IWN4965_EEPROM_VOLTAGE 0x0e9
+#define IWN4965_EEPROM_BANDS 0x0ea
+/* Indirect offsets. */
+#define IWN5000_EEPROM_DOMAIN 0x001
+#define IWN5000_EEPROM_BAND1 0x004
+#define IWN5000_EEPROM_BAND2 0x013
+#define IWN5000_EEPROM_BAND3 0x021
+#define IWN5000_EEPROM_BAND4 0x02e
+#define IWN5000_EEPROM_BAND5 0x03a
+#define IWN5000_EEPROM_BAND6 0x041
+#define IWN5000_EEPROM_BAND7 0x049
+#define IWN6000_EEPROM_ENHINFO 0x054
+#define IWN5000_EEPROM_CRYSTAL 0x128
+#define IWN5000_EEPROM_TEMP 0x12a
+#define IWN5000_EEPROM_VOLT 0x12b
+#define IWN2000_EEPROM_RAWTEMP 0x12b
+
+/* Possible flags for IWN_EEPROM_SKU_CAP. */
+#define IWN_EEPROM_SKU_CAP_11N (1 << 6)
+#define IWN_EEPROM_SKU_CAP_AMT (1 << 7)
+#define IWN_EEPROM_SKU_CAP_IPAN (1 << 8)
+
+/* Possible flags for IWN_EEPROM_RFCFG. */
+#define IWN_RFCFG_TYPE(x) (((x) >> 0) & 0x3)
+#define IWN_RFCFG_STEP(x) (((x) >> 2) & 0x3)
+#define IWN_RFCFG_DASH(x) (((x) >> 4) & 0x3)
+#define IWN_RFCFG_TXANTMSK(x) (((x) >> 8) & 0xf)
+#define IWN_RFCFG_RXANTMSK(x) (((x) >> 12) & 0xf)
+
+struct iwn_eeprom_chan {
+ uint8_t flags;
+#define IWN_EEPROM_CHAN_VALID (1 << 0)
+#define IWN_EEPROM_CHAN_IBSS (1 << 1)
+#define IWN_EEPROM_CHAN_ACTIVE (1 << 3)
+#define IWN_EEPROM_CHAN_RADAR (1 << 4)
+#define IWN_EEPROM_CHAN_WIDE (1 << 5)
+#define IWN_EEPROM_CHAN_DFS (1 << 7)
+
+ int8_t maxpwr;
+} __packed;
+
+struct iwn_eeprom_enhinfo {
+ uint16_t chan;
+ int8_t chain[3]; /* max power in half-dBm */
+ uint8_t reserved;
+ int8_t mimo2; /* max power in half-dBm */
+ int8_t mimo3; /* max power in half-dBm */
+} __packed;
+
+struct iwn5000_eeprom_calib_hdr {
+ uint8_t version;
+ uint8_t pa_type;
+ uint16_t volt;
+} __packed;
+
+#define IWN_NSAMPLES 3
+struct iwn4965_eeprom_chan_samples {
+ uint8_t num;
+ struct {
+ uint8_t temp;
+ uint8_t gain;
+ uint8_t power;
+ int8_t pa_det;
+ } samples[2][IWN_NSAMPLES];
+} __packed;
+
+#define IWN_NBANDS 8
+struct iwn4965_eeprom_band {
+ uint8_t lo; /* low channel number */
+ uint8_t hi; /* high channel number */
+ struct iwn4965_eeprom_chan_samples chans[2];
+} __packed;
+
+/*
+ * Offsets of channels descriptions in EEPROM.
+ */
+static const uint32_t iwn4965_regulatory_bands[IWN_NBANDS] = {
+ IWN4965_EEPROM_BAND1,
+ IWN4965_EEPROM_BAND2,
+ IWN4965_EEPROM_BAND3,
+ IWN4965_EEPROM_BAND4,
+ IWN4965_EEPROM_BAND5,
+ IWN4965_EEPROM_BAND6,
+ IWN4965_EEPROM_BAND7
+};
+
+static const uint32_t iwn5000_regulatory_bands[IWN_NBANDS] = {
+ IWN5000_EEPROM_BAND1,
+ IWN5000_EEPROM_BAND2,
+ IWN5000_EEPROM_BAND3,
+ IWN5000_EEPROM_BAND4,
+ IWN5000_EEPROM_BAND5,
+ IWN5000_EEPROM_BAND6,
+ IWN5000_EEPROM_BAND7
+};
+
+#define IWN_CHAN_BANDS_COUNT 7
+#define IWN_MAX_CHAN_PER_BAND 14
+static const struct iwn_chan_band {
+ uint8_t nchan;
+ uint8_t chan[IWN_MAX_CHAN_PER_BAND];
+} iwn_bands[] = {
+ /* 20MHz channels, 2GHz band. */
+ { 14, { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 } },
+ /* 20MHz channels, 5GHz band. */
+ { 13, { 183, 184, 185, 187, 188, 189, 192, 196, 7, 8, 11, 12, 16 } },
+ { 12, { 34, 36, 38, 40, 42, 44, 46, 48, 52, 56, 60, 64 } },
+ { 11, { 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140 } },
+ { 6, { 145, 149, 153, 157, 161, 165 } },
+ /* 40MHz channels (primary channels), 2GHz band. */
+ { 7, { 1, 2, 3, 4, 5, 6, 7 } },
+ /* 40MHz channels (primary channels), 5GHz band. */
+ { 11, { 36, 44, 52, 60, 100, 108, 116, 124, 132, 149, 157 } }
+};
+
+#define IWN1000_OTP_NBLOCKS 3
+#define IWN6000_OTP_NBLOCKS 4
+#define IWN6050_OTP_NBLOCKS 7
+
+/* HW rate indices. */
+#define IWN_RIDX_CCK1 0
+#define IWN_RIDX_OFDM6 4
+
+static const struct iwn_rate {
+ uint8_t rate;
+ uint8_t plcp;
+ uint8_t flags;
+} iwn_rates[IWN_RIDX_MAX + 1] = {
+ { 2, 10, IWN_RFLAG_CCK },
+ { 4, 20, IWN_RFLAG_CCK },
+ { 11, 55, IWN_RFLAG_CCK },
+ { 22, 110, IWN_RFLAG_CCK },
+ { 12, 0xd, 0 },
+ { 18, 0xf, 0 },
+ { 24, 0x5, 0 },
+ { 36, 0x7, 0 },
+ { 48, 0x9, 0 },
+ { 72, 0xb, 0 },
+ { 96, 0x1, 0 },
+ { 108, 0x3, 0 },
+ { 120, 0x3, 0 }
+};
+
+#define IWN4965_MAX_PWR_INDEX 107
+
+/*
+ * RF Tx gain values from highest to lowest power (values obtained from
+ * the reference driver.)
+ */
+static const uint8_t iwn4965_rf_gain_2ghz[IWN4965_MAX_PWR_INDEX + 1] = {
+ 0x3f, 0x3f, 0x3f, 0x3e, 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c,
+ 0x3c, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39, 0x39, 0x39, 0x38,
+ 0x38, 0x38, 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x35, 0x35, 0x35,
+ 0x34, 0x34, 0x34, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x31, 0x31,
+ 0x31, 0x30, 0x30, 0x30, 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x04,
+ 0x04, 0x04, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8_t iwn4965_rf_gain_5ghz[IWN4965_MAX_PWR_INDEX + 1] = {
+ 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3e, 0x3e, 0x3e, 0x3d, 0x3d, 0x3d,
+ 0x3c, 0x3c, 0x3c, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39, 0x39,
+ 0x39, 0x38, 0x38, 0x38, 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x35,
+ 0x35, 0x35, 0x34, 0x34, 0x34, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32,
+ 0x31, 0x31, 0x31, 0x30, 0x30, 0x30, 0x25, 0x25, 0x25, 0x24, 0x24,
+ 0x24, 0x23, 0x23, 0x23, 0x22, 0x18, 0x18, 0x17, 0x17, 0x17, 0x16,
+ 0x16, 0x16, 0x15, 0x15, 0x15, 0x14, 0x14, 0x14, 0x13, 0x13, 0x13,
+ 0x12, 0x08, 0x08, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06, 0x05, 0x05,
+ 0x05, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/*
+ * DSP pre-DAC gain values from highest to lowest power (values obtained
+ * from the reference driver.)
+ */
+static const uint8_t iwn4965_dsp_gain_2ghz[IWN4965_MAX_PWR_INDEX + 1] = {
+ 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68,
+ 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e,
+ 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62,
+ 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68,
+ 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e,
+ 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62,
+ 0x6e, 0x68, 0x62, 0x61, 0x60, 0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a,
+ 0x59, 0x58, 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51, 0x50, 0x4f,
+ 0x4e, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, 0x48, 0x47, 0x46, 0x45, 0x44,
+ 0x43, 0x42, 0x41, 0x40, 0x3f, 0x3e, 0x3d, 0x3c, 0x3b
+};
+
+static const uint8_t iwn4965_dsp_gain_5ghz[IWN4965_MAX_PWR_INDEX + 1] = {
+ 0x7b, 0x75, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62,
+ 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68,
+ 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e,
+ 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62,
+ 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68,
+ 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e,
+ 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62,
+ 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68,
+ 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e, 0x68, 0x62, 0x6e,
+ 0x68, 0x62, 0x6e, 0x68, 0x62, 0x5d, 0x58, 0x53, 0x4e
+};
+
+/*
+ * Power saving settings (values obtained from the reference driver.)
+ */
+#define IWN_NDTIMRANGES 3
+#define IWN_NPOWERLEVELS 6
+static const struct iwn_pmgt {
+ uint32_t rxtimeout;
+ uint32_t txtimeout;
+ int32_t intval[5];
+ int skip_dtim;
+} iwn_pmgt[IWN_NDTIMRANGES][IWN_NPOWERLEVELS] = {
+ /* DTIM <= 2 */
+ {
+ { 0, 0, { 0, 0, 0, 0, 0 }, 0 }, /* CAM */
+ { 200, 500, { 1, 2, 2, 2, -1 }, 0 }, /* PS level 1 */
+ { 200, 300, { 1, 2, 2, 2, -1 }, 0 }, /* PS level 2 */
+ { 50, 100, { 2, 2, 2, 2, -1 }, 0 }, /* PS level 3 */
+ { 50, 25, { 2, 2, 4, 4, -1 }, 1 }, /* PS level 4 */
+ { 25, 25, { 2, 2, 4, 6, -1 }, 2 } /* PS level 5 */
+ },
+ /* 3 <= DTIM <= 10 */
+ {
+ { 0, 0, { 0, 0, 0, 0, 0 }, 0 }, /* CAM */
+ { 200, 500, { 1, 2, 3, 4, 4 }, 0 }, /* PS level 1 */
+ { 200, 300, { 1, 2, 3, 4, 7 }, 0 }, /* PS level 2 */
+ { 50, 100, { 2, 4, 6, 7, 9 }, 0 }, /* PS level 3 */
+ { 50, 25, { 2, 4, 6, 9, 10 }, 1 }, /* PS level 4 */
+ { 25, 25, { 2, 4, 7, 10, 10 }, 2 } /* PS level 5 */
+ },
+ /* DTIM >= 11 */
+ {
+ { 0, 0, { 0, 0, 0, 0, 0 }, 0 }, /* CAM */
+ { 200, 500, { 1, 2, 3, 4, -1 }, 0 }, /* PS level 1 */
+ { 200, 300, { 2, 4, 6, 7, -1 }, 0 }, /* PS level 2 */
+ { 50, 100, { 2, 7, 9, 9, -1 }, 0 }, /* PS level 3 */
+ { 50, 25, { 2, 7, 9, 9, -1 }, 0 }, /* PS level 4 */
+ { 25, 25, { 4, 7, 10, 10, -1 }, 0 } /* PS level 5 */
+ }
+};
+
+struct iwn_sensitivity_limits {
+ uint32_t min_ofdm_x1;
+ uint32_t max_ofdm_x1;
+ uint32_t min_ofdm_mrc_x1;
+ uint32_t max_ofdm_mrc_x1;
+ uint32_t min_ofdm_x4;
+ uint32_t max_ofdm_x4;
+ uint32_t min_ofdm_mrc_x4;
+ uint32_t max_ofdm_mrc_x4;
+ uint32_t min_cck_x4;
+ uint32_t max_cck_x4;
+ uint32_t min_cck_mrc_x4;
+ uint32_t max_cck_mrc_x4;
+ uint32_t min_energy_cck;
+ uint32_t energy_cck;
+ uint32_t energy_ofdm;
+};
+
+/*
+ * RX sensitivity limits (values obtained from the reference driver.)
+ */
+static const struct iwn_sensitivity_limits iwn4965_sensitivity_limits = {
+ 105, 140,
+ 220, 270,
+ 85, 120,
+ 170, 210,
+ 125, 200,
+ 200, 400,
+ 97,
+ 100,
+ 100
+};
+
+static const struct iwn_sensitivity_limits iwn5000_sensitivity_limits = {
+ 120, 120, /* min = max for performance bug in DSP. */
+ 240, 240, /* min = max for performance bug in DSP. */
+ 90, 120,
+ 170, 210,
+ 125, 200,
+ 170, 400,
+ 95,
+ 95,
+ 95
+};
+
+static const struct iwn_sensitivity_limits iwn5150_sensitivity_limits = {
+ 105, 105, /* min = max for performance bug in DSP. */
+ 220, 220, /* min = max for performance bug in DSP. */
+ 90, 120,
+ 170, 210,
+ 125, 200,
+ 170, 400,
+ 95,
+ 95,
+ 95
+};
+
+static const struct iwn_sensitivity_limits iwn1000_sensitivity_limits = {
+ 120, 155,
+ 240, 290,
+ 90, 120,
+ 170, 210,
+ 125, 200,
+ 170, 400,
+ 95,
+ 95,
+ 95
+};
+
+static const struct iwn_sensitivity_limits iwn6000_sensitivity_limits = {
+ 105, 110,
+ 192, 232,
+ 80, 145,
+ 128, 232,
+ 125, 175,
+ 160, 310,
+ 97,
+ 97,
+ 100
+};
+
+static const struct iwn_sensitivity_limits iwn2000_sensitivity_limits = {
+ 105, 110,
+ 192, 232,
+ 80, 145,
+ 128, 232,
+ 125, 175,
+ 160, 310,
+ 97,
+ 97,
+ 100
+};
+
+#ifndef IEEE80211_NO_HT
+/* Map TID to TX scheduler's FIFO. */
+static const uint8_t iwn_tid2fifo[] = {
+ 1, 0, 0, 1, 2, 2, 3, 3, 7, 7, 7, 7, 7, 7, 7, 7, 3
+};
+#endif
+
+#ifdef notyet
+/* WiFi/WiMAX coexist event priority table for 6050. */
+static const struct iwn5000_wimax_event iwn6050_wimax_events[] = {
+ { 0x04, 0x03, 0x00, 0x00 },
+ { 0x04, 0x03, 0x00, 0x03 },
+ { 0x04, 0x03, 0x00, 0x03 },
+ { 0x04, 0x03, 0x00, 0x03 },
+ { 0x04, 0x03, 0x00, 0x00 },
+ { 0x04, 0x03, 0x00, 0x07 },
+ { 0x04, 0x03, 0x00, 0x00 },
+ { 0x04, 0x03, 0x00, 0x03 },
+ { 0x04, 0x03, 0x00, 0x03 },
+ { 0x04, 0x03, 0x00, 0x00 },
+ { 0x06, 0x03, 0x00, 0x07 },
+ { 0x04, 0x03, 0x00, 0x00 },
+ { 0x06, 0x06, 0x00, 0x03 },
+ { 0x04, 0x03, 0x00, 0x07 },
+ { 0x04, 0x03, 0x00, 0x00 },
+ { 0x04, 0x03, 0x00, 0x00 }
+};
+#endif
+
+/* Firmware errors. */
+static const char * const iwn_fw_errmsg[] = {
+ "OK",
+ "FAIL",
+ "BAD_PARAM",
+ "BAD_CHECKSUM",
+ "NMI_INTERRUPT_WDG",
+ "SYSASSERT",
+ "FATAL_ERROR",
+ "BAD_COMMAND",
+ "HW_ERROR_TUNE_LOCK",
+ "HW_ERROR_TEMPERATURE",
+ "ILLEGAL_CHAN_FREQ",
+ "VCC_NOT_STABLE",
+ "FH_ERROR",
+ "NMI_INTERRUPT_HOST",
+ "NMI_INTERRUPT_ACTION_PT",
+ "NMI_INTERRUPT_UNKNOWN",
+ "UCODE_VERSION_MISMATCH",
+ "HW_ERROR_ABS_LOCK",
+ "HW_ERROR_CAL_LOCK_FAIL",
+ "NMI_INTERRUPT_INST_ACTION_PT",
+ "NMI_INTERRUPT_DATA_ACTION_PT",
+ "NMI_TRM_HW_ER",
+ "NMI_INTERRUPT_TRM",
+ "NMI_INTERRUPT_BREAKPOINT"
+ "DEBUG_0",
+ "DEBUG_1",
+ "DEBUG_2",
+ "DEBUG_3",
+ "ADVANCED_SYSASSERT"
+};
+
+/* Find least significant bit that is set. */
+#define IWN_LSB(x) ((((x) - 1) & (x)) ^ (x))
+
+#define IWN_READ(sc, reg) \
+ iwn_read(sc, reg)
+
+#define IWN_WRITE(sc, reg, val) \
+ iwn_write(sc, reg, val)
+
+#define IWN_WRITE_1(sc, reg, val) \
+ iwn_write_1(sc, reg, val)
+
+#define IWN_SETBITS(sc, reg, mask) \
+ IWN_WRITE(sc, reg, IWN_READ(sc, reg) | (mask))
+
+#define IWN_CLRBITS(sc, reg, mask) \
+ IWN_WRITE(sc, reg, IWN_READ(sc, reg) & ~(mask))
+
+#define IWN_BARRIER_WRITE(sc) \
+ membar_producer()
+
+#define IWN_BARRIER_READ_WRITE(sc) \
+ (membar_producer(), membar_consumer())
+
+#endif /* _IF_IWNREG_H */
diff --git a/usr/src/uts/common/io/iwn/if_iwnvar.h b/usr/src/uts/common/io/iwn/if_iwnvar.h
new file mode 100644
index 0000000000..fcf32f49a3
--- /dev/null
+++ b/usr/src/uts/common/io/iwn/if_iwnvar.h
@@ -0,0 +1,421 @@
+/* $NetBSD: if_iwnvar.h,v 1.17 2015/09/22 23:23:06 nonaka Exp $ */
+/* $OpenBSD: if_iwnvar.h,v 1.28 2014/09/09 18:55:08 sthen Exp $ */
+
+/*-
+ * Copyright (c) 2007, 2008
+ * Damien Bergamini <damien.bergamini@free.fr>
+ *
+ * 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.
+ */
+
+/*
+ * Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+ */
+
+#ifndef _IF_IWNVAR_H
+#define _IF_IWNVAR_H
+
+#include <sys/net80211.h>
+#include <sys/queue.h>
+
+struct iwn_dma_info {
+ ddi_dma_handle_t dma_hdl;
+ ddi_acc_handle_t acc_hdl;
+ ddi_dma_cookie_t cookie;
+ uint_t ncookies;
+ uintptr_t paddr;
+ caddr_t vaddr;
+ size_t size;
+ size_t length;
+};
+
+struct iwn_tx_data {
+ struct iwn_dma_info dma_data;
+ uintptr_t cmd_paddr;
+ uintptr_t scratch_paddr;
+ struct iwn_dma_info cmd_dma;
+ struct ieee80211_node *ni;
+};
+
+struct iwn_tx_ring {
+ struct iwn_dma_info desc_dma;
+ struct iwn_dma_info cmd_dma;
+ struct iwn_tx_desc *desc;
+ struct iwn_tx_cmd *cmd;
+ struct iwn_tx_data data[IWN_TX_RING_COUNT];
+ int qid;
+ int queued;
+ int cur;
+};
+
+#define IWN_RBUF_COUNT (IWN_RX_RING_COUNT + 32)
+
+struct iwn_softc;
+
+struct iwn_rx_data {
+ struct iwn_dma_info dma_data;
+};
+
+struct iwn_rx_ring {
+ struct iwn_dma_info desc_dma;
+ struct iwn_dma_info stat_dma;
+ uint32_t *desc;
+ struct iwn_rx_status *stat;
+ struct iwn_rx_data data[IWN_RX_RING_COUNT];
+ int cur;
+};
+
+struct iwn_node {
+ struct ieee80211_node ni; /* must be the first */
+ struct ieee80211_amrr_node amn;
+ uint16_t disable_tid;
+ uint8_t id;
+ uint8_t ridx[IEEE80211_RATE_MAXSIZE];
+};
+
+struct iwn_calib_state {
+ uint8_t state;
+#define IWN_CALIB_STATE_INIT 0
+#define IWN_CALIB_STATE_ASSOC 1
+#define IWN_CALIB_STATE_RUN 2
+
+ u_int nbeacons;
+ int32_t noise[3];
+ uint32_t rssi[3];
+ uint32_t ofdm_x1;
+ uint32_t ofdm_mrc_x1;
+ uint32_t ofdm_x4;
+ uint32_t ofdm_mrc_x4;
+ uint32_t cck_x4;
+ uint32_t cck_mrc_x4;
+ uint32_t bad_plcp_ofdm;
+ uint32_t fa_ofdm;
+ uint32_t bad_plcp_cck;
+ uint32_t fa_cck;
+ uint32_t low_fa;
+ uint8_t cck_state;
+#define IWN_CCK_STATE_INIT 0
+#define IWN_CCK_STATE_LOFA 1
+#define IWN_CCK_STATE_HIFA 2
+
+ uint8_t noise_samples[20];
+ u_int cur_noise_sample;
+ uint8_t noise_ref;
+ uint32_t energy_samples[10];
+ u_int cur_energy_sample;
+ uint32_t energy_cck;
+};
+
+struct iwn_calib_info {
+ uint8_t *buf;
+ u_int len;
+};
+
+struct iwn_fw_part {
+ const uint8_t *text;
+ uint32_t textsz;
+ const uint8_t *data;
+ uint32_t datasz;
+};
+
+struct iwn_fw_info {
+ u_char *data;
+ size_t size;
+ struct iwn_fw_part init;
+ struct iwn_fw_part main;
+ struct iwn_fw_part boot;
+};
+
+struct iwn_ops {
+ int (*load_firmware)(struct iwn_softc *);
+ void (*read_eeprom)(struct iwn_softc *);
+ int (*post_alive)(struct iwn_softc *);
+ int (*nic_config)(struct iwn_softc *);
+ int (*config_bt_coex)(struct iwn_softc *);
+ void (*update_sched)(struct iwn_softc *, int, int, uint8_t,
+ uint16_t);
+ int (*get_temperature)(struct iwn_softc *);
+ int (*get_rssi)(const struct iwn_rx_stat *);
+ int (*set_txpower)(struct iwn_softc *, int);
+ int (*init_gains)(struct iwn_softc *);
+ int (*set_gains)(struct iwn_softc *);
+ int (*add_node)(struct iwn_softc *, struct iwn_node_info *,
+ int);
+ void (*tx_done)(struct iwn_softc *, struct iwn_rx_desc *,
+ struct iwn_rx_data *);
+#ifndef IEEE80211_NO_HT
+ void (*ampdu_tx_start)(struct iwn_softc *,
+ struct ieee80211_node *, uint8_t, uint16_t);
+ void (*ampdu_tx_stop)(struct iwn_softc *, uint8_t,
+ uint16_t);
+#endif
+};
+
+struct iwn_softc {
+ struct ieee80211com sc_ic;
+ dev_info_t *sc_dip;
+
+ int (*sc_newstate)(struct ieee80211com *,
+ enum ieee80211_state, int);
+
+ enum ieee80211_state sc_ostate;
+
+ clock_t sc_clk;
+ struct ieee80211_amrr amrr;
+ uint8_t fixed_ridx;
+
+ uint16_t sc_devid;
+ caddr_t sc_base;
+
+ u_int sc_flags;
+#define IWN_FLAG_HAS_5GHZ (1 << 0)
+#define IWN_FLAG_HAS_OTPROM (1 << 1)
+#define IWN_FLAG_CALIB_DONE (1 << 2)
+#define IWN_FLAG_USE_ICT (1 << 3)
+#define IWN_FLAG_INTERNAL_PA (1 << 4)
+#define IWN_FLAG_FW_DMA (1 << 5)
+#define IWN_FLAG_HAS_11N (1 << 6)
+#define IWN_FLAG_ENH_SENS (1 << 7)
+/* Added for NetBSD */
+#define IWN_FLAG_HW_INITED (1 << 8)
+#define IWN_FLAG_SCANNING_2GHZ (1 << 9)
+#define IWN_FLAG_SCANNING_5GHZ (1 << 10)
+#define IWN_FLAG_SCANNING (IWN_FLAG_SCANNING_2GHZ|IWN_FLAG_SCANNING_5GHZ)
+/* From iwp.c */
+#define IWN_FLAG_ATTACHED (1 << 11)
+#define IWN_FLAG_CMD_DONE (1 << 12)
+#define IWN_FLAG_FW_ALIVE (1 << 13)
+#define IWN_FLAG_HW_ERR_RECOVER (1 << 14)
+#define IWN_FLAG_RATE_AUTO_CTL (1 << 15)
+#define IWN_FLAG_RUNNING (1 << 16)
+#define IWN_FLAG_SUSPEND (1 << 17)
+#define IWN_FLAG_RADIO_OFF (1 << 18)
+#define IWN_FLAG_STATISTICS (1 << 19)
+#define IWN_FLAG_READY (1 << 20)
+#define IWN_FLAG_PUT_SEG (1 << 21)
+#define IWN_FLAG_QUIESCED (1 << 22)
+#define IWN_FLAG_LAZY_RESUME (1 << 23)
+#define IWN_FLAG_STOP_CALIB_TO (1 << 24)
+
+ uint8_t hw_type;
+
+ struct iwn_ops ops;
+ const char *fwname;
+ const struct iwn_sensitivity_limits
+ *limits;
+ int ntxqs;
+ int ndmachnls;
+ uint8_t broadcast_id;
+ int rxonsz;
+ int schedsz;
+ uint32_t fw_text_maxsz;
+ uint32_t fw_data_maxsz;
+ uint32_t fwsz;
+ uint32_t sched_txfact_addr;
+
+ /* TX scheduler rings. */
+ struct iwn_dma_info sched_dma;
+ uint16_t *sched;
+ uint32_t sched_base;
+
+ /* "Keep Warm" page. */
+ struct iwn_dma_info kw_dma;
+
+ /* Firmware DMA transfer. */
+ struct iwn_dma_info fw_dma;
+
+ /* ICT table. */
+ struct iwn_dma_info ict_dma;
+ uint32_t *ict;
+ int ict_cur;
+
+ /* TX/RX rings. */
+ struct iwn_tx_ring txq[IWN5000_NTXQUEUES];
+ struct iwn_rx_ring rxq;
+
+ ddi_acc_handle_t sc_regh;
+ void *sc_ih;
+ ddi_acc_handle_t sc_pcih;
+ uint_t sc_intr_pri;
+ int sc_intr_cap;
+ int sc_intr_count;
+ size_t sc_intr_size;
+ ddi_intr_handle_t *sc_intr_htable;
+ int sc_cap_off; /* PCIe Capabilities. */
+
+ ddi_periodic_t sc_periodic;
+ timeout_id_t scan_to;
+ timeout_id_t calib_to;
+ int calib_cnt;
+ struct iwn_calib_state calib;
+
+
+ struct iwn_fw_info fw;
+ struct iwn_calib_info calibcmd[5];
+ uint32_t errptr;
+
+ struct iwn_rx_stat last_rx_stat;
+ int last_rx_valid;
+ struct iwn_ucode_info ucode_info;
+ struct iwn_rxon rxon;
+ struct iwn_rxon rxon_save;
+ uint32_t rawtemp;
+ int temp;
+ int noise;
+ uint32_t qfullmsk;
+
+ uint32_t prom_base;
+ struct iwn4965_eeprom_band
+ bands[IWN_NBANDS];
+ uint16_t rfcfg;
+ uint8_t calib_ver;
+ char eeprom_domain[4];
+ uint32_t eeprom_crystal;
+ int16_t eeprom_temp;
+ int16_t eeprom_voltage;
+ int16_t eeprom_rawtemp;
+ int8_t maxpwr2GHz;
+ int8_t maxpwr5GHz;
+ int8_t maxpwr[IEEE80211_CHAN_MAX];
+ int8_t enh_maxpwr[35];
+
+ uint8_t reset_noise_gain;
+ uint8_t noise_gain;
+
+ uint32_t tlv_feature_flags;
+
+ int32_t temp_off;
+ uint32_t int_mask;
+ uint8_t ntxchains;
+ uint8_t nrxchains;
+ uint8_t txchainmask;
+ uint8_t rxchainmask;
+ uint8_t chainmask;
+
+ int sc_tx_timer;
+ void *powerhook;
+
+ kmutex_t sc_mtx; /* mutex for init/stop */
+ kmutex_t sc_tx_mtx;
+ kmutex_t sc_mt_mtx;
+
+ kcondvar_t sc_cmd_cv;
+ kcondvar_t sc_scan_cv;
+ kcondvar_t sc_fhdma_cv;
+ kcondvar_t sc_alive_cv;
+ kcondvar_t sc_calib_cv;
+ kcondvar_t sc_mt_cv;
+
+ volatile uint32_t sc_cmd_flag;
+ volatile uint32_t sc_cmd_accum;
+#define SC_CMD_FLG_NONE (0)
+#define SC_CMD_FLG_PENDING (1)
+#define SC_CMD_FLG_DONE (2)
+
+ /* kstats */
+ uint32_t sc_tx_nobuf;
+ uint32_t sc_rx_nobuf;
+ uint32_t sc_tx_err;
+ uint32_t sc_rx_err;
+ uint32_t sc_tx_retries;
+
+ kstat_t *sc_ks_misc;
+ struct iwn_ks_misc *sc_misc;
+ kstat_t *sc_ks_ant;
+ struct iwn_ks_ant *sc_ant;
+ kstat_t *sc_ks_sens;
+ struct iwn_ks_sens *sc_sens;
+ kstat_t *sc_ks_timing;
+ struct iwn_ks_timing *sc_timing;
+ kstat_t *sc_ks_edca;
+ struct iwn_ks_edca *sc_edca;
+ kstat_t *sc_ks_txpower;
+ struct iwn_ks_txpower *sc_txpower;
+ kstat_t *sc_ks_toff;
+ union {
+ struct iwn_ks_toff_2000 *t2000;
+ struct iwn_ks_toff_6000 *t6000;
+ } sc_toff;
+};
+
+struct iwn_ks_misc {
+ kstat_named_t temp;
+ kstat_named_t crit_temp;
+ kstat_named_t pslevel;
+ kstat_named_t noise;
+};
+
+struct iwn_ks_ant {
+ kstat_named_t tx_ant;
+ kstat_named_t rx_ant;
+ kstat_named_t conn_ant;
+ kstat_named_t gain[3];
+};
+
+struct iwn_ks_sens {
+ kstat_named_t ofdm_x1;
+ kstat_named_t ofdm_mrc_x1;
+ kstat_named_t ofdm_x4;
+ kstat_named_t ofdm_mrc_x4;
+ kstat_named_t cck_x4;
+ kstat_named_t cck_mrc_x4;
+ kstat_named_t energy_cck;
+};
+
+struct iwn_ks_timing {
+ kstat_named_t bintval;
+ kstat_named_t tstamp;
+ kstat_named_t init;
+};
+
+struct iwn_ks_edca {
+ struct {
+ kstat_named_t cwmin;
+ kstat_named_t cwmax;
+ kstat_named_t aifsn;
+ kstat_named_t txop;
+ } ac[4];
+};
+
+struct iwn_ks_txpower {
+ kstat_named_t vdiff;
+ kstat_named_t chan;
+ kstat_named_t group;
+ kstat_named_t subband;
+ struct {
+ kstat_named_t power;
+ kstat_named_t gain;
+ kstat_named_t temp;
+ kstat_named_t tcomp;
+ struct {
+ kstat_named_t rf_gain;
+ kstat_named_t dsp_gain;
+ } rate[IWN_RIDX_MAX];
+ } txchain[2];
+};
+
+struct iwn_ks_toff_2000 {
+ kstat_named_t toff_lo;
+ kstat_named_t toff_hi;
+ kstat_named_t volt;
+};
+
+struct iwn_ks_toff_6000 {
+ kstat_named_t toff;
+};
+
+#define IWN_CHK_FAST_RECOVER(sc) \
+ (sc->sc_ic.ic_state == IEEE80211_S_RUN && \
+ sc->sc_ic.ic_opmode == IEEE80211_M_STA)
+
+#endif /* _IF_IWNVAR_H */
diff --git a/usr/src/uts/common/io/lofi.c b/usr/src/uts/common/io/lofi.c
index c358b0d067..b2af209d87 100644
--- a/usr/src/uts/common/io/lofi.c
+++ b/usr/src/uts/common/io/lofi.c
@@ -79,6 +79,25 @@
* addition, the DKIOCSTATE ioctl will return DKIO_DEV_GONE when the device is
* detached but not removed.
*
+ * If detach was requested and lofi device is not open, we will perform
+ * unmap and remove the lofi instance.
+ *
+ * If the lofi device is open and the li_cleanup is set on ioctl request,
+ * we set ls_cleanup flag to notify the cleanup is requested, and the
+ * last lofi_close will perform the unmapping and this lofi instance will be
+ * removed.
+ *
+ * If the lofi device is open and the li_force is set on ioctl request,
+ * we set ls_cleanup flag to notify the cleanup is requested,
+ * we also set ls_vp_closereq to notify IO tasks to return EIO on new
+ * IO requests and wait in process IO count to become 0, indicating there
+ * are no more IO requests. Since ls_cleanup is set, the last lofi_close
+ * will perform unmap and this lofi instance will be removed.
+ * See also lofi_unmap_file() for details.
+ *
+ * Once ls_cleanup is set for the instance, we do not allow lofi_open()
+ * calls to succeed and can have last lofi_close() to remove the instance.
+ *
* Known problems:
*
* UFS logging. Mounting a UFS filesystem image "logging"
@@ -290,6 +309,17 @@ is_opened(struct lofi_state *lsp)
}
static void
+lofi_set_cleanup(struct lofi_state *lsp)
+{
+ ASSERT(MUTEX_HELD(&lofi_lock));
+
+ lsp->ls_cleanup = B_TRUE;
+
+ /* wake up any threads waiting on dkiocstate */
+ cv_broadcast(&lsp->ls_vp_cv);
+}
+
+static void
lofi_free_crypto(struct lofi_state *lsp)
{
ASSERT(MUTEX_HELD(&lofi_lock));
@@ -495,6 +525,15 @@ lofi_destroy(struct lofi_state *lsp, cred_t *credp)
ASSERT(MUTEX_HELD(&lofi_lock));
+ /*
+ * Before we can start to release the other resources,
+ * make sure we have all tasks completed and taskq removed.
+ */
+ if (lsp->ls_taskq != NULL) {
+ taskq_destroy(lsp->ls_taskq);
+ lsp->ls_taskq = NULL;
+ }
+
list_remove(&lofi_list, lsp);
lofi_free_crypto(lsp);
@@ -520,12 +559,12 @@ lofi_destroy(struct lofi_state *lsp, cred_t *credp)
}
if (lsp->ls_stacked_vp != lsp->ls_vp)
VN_RELE(lsp->ls_stacked_vp);
+ lsp->ls_vp = lsp->ls_stacked_vp = NULL;
- if (lsp->ls_taskq != NULL)
- taskq_destroy(lsp->ls_taskq);
-
- if (lsp->ls_kstat != NULL)
+ if (lsp->ls_kstat != NULL) {
kstat_delete(lsp->ls_kstat);
+ lsp->ls_kstat = NULL;
+ }
/*
* Free cached decompressed segment data
@@ -547,6 +586,7 @@ lofi_destroy(struct lofi_state *lsp, cred_t *credp)
mutex_destroy(&lsp->ls_vp_lock);
cv_destroy(&lsp->ls_vp_cv);
lsp->ls_vp_ready = B_FALSE;
+ lsp->ls_vp_closereq = B_FALSE;
ASSERT(ddi_get_soft_state(lofi_statep, id) == lsp);
(void) ndi_devi_offline(lsp->ls_dip, NDI_DEVI_REMOVE);
@@ -592,7 +632,7 @@ lofi_zone_shutdown(zoneid_t zoneid, void *arg)
* user destroys the device.
*/
if (is_opened(lsp)) {
- lsp->ls_cleanup = 1;
+ lofi_set_cleanup(lsp);
} else {
lofi_free_dev(lsp);
lofi_destroy(lsp, kcred);
@@ -645,6 +685,11 @@ lofi_open(dev_t *devp, int flag, int otyp, struct cred *credp)
return (EINVAL);
}
+ if (lsp->ls_cleanup == B_TRUE) {
+ mutex_exit(&lofi_lock);
+ return (ENXIO);
+ }
+
if (lsp->ls_vp == NULL) {
mutex_exit(&lofi_lock);
return (ENXIO);
@@ -747,7 +792,8 @@ lofi_close(dev_t dev, int flag, int otyp, struct cred *credp)
* asked for cleanup (li_cleanup), finish up if we're the last
* out of the door.
*/
- if (!is_opened(lsp) && (lsp->ls_cleanup || lsp->ls_vp == NULL)) {
+ if (!is_opened(lsp) &&
+ (lsp->ls_cleanup == B_TRUE || lsp->ls_vp == NULL)) {
lofi_free_dev(lsp);
lofi_destroy(lsp, credp);
}
@@ -1618,8 +1664,18 @@ lofi_strategy(struct buf *bp)
biodone(bp);
return (0);
}
- shift = lsp->ls_lbshift;
+ /* Check if we are closing. */
+ mutex_enter(&lsp->ls_vp_lock);
+ if (lsp->ls_vp == NULL || lsp->ls_vp_closereq) {
+ mutex_exit(&lsp->ls_vp_lock);
+ bioerror(bp, EIO);
+ biodone(bp);
+ return (0);
+ }
+ mutex_exit(&lsp->ls_vp_lock);
+
+ shift = lsp->ls_lbshift;
p_lba = 0;
p_nblks = lsp->ls_vp_size >> shift;
@@ -1642,13 +1698,6 @@ lofi_strategy(struct buf *bp)
offset = (bp->b_lblkno+p_lba) << shift; /* offset within file */
mutex_enter(&lsp->ls_vp_lock);
- if (lsp->ls_vp == NULL || lsp->ls_vp_closereq) {
- bioerror(bp, EIO);
- biodone(bp);
- mutex_exit(&lsp->ls_vp_lock);
- return (0);
- }
-
if (lsp->ls_crypto_enabled) {
/* encrypted data really begins after crypto header */
offset += lsp->ls_crypto_offset;
@@ -2904,9 +2953,6 @@ lofi_unmap_file(struct lofi_ioctl *ulip, int byfilename,
{
struct lofi_state *lsp;
struct lofi_ioctl *klip;
- nvlist_t *nvl = NULL;
- clock_t ticks;
- char name[MAXNAMELEN];
int err;
err = copy_in_lofi_ioctl(ulip, &klip, ioctl_flag);
@@ -2917,21 +2963,18 @@ lofi_unmap_file(struct lofi_ioctl *ulip, int byfilename,
if (byfilename) {
if ((err = file_to_lofi(klip->li_filename, klip->li_readonly,
&lsp)) != 0) {
- mutex_exit(&lofi_lock);
- return (err);
+ goto done;
}
} else if (klip->li_id == 0) {
- mutex_exit(&lofi_lock);
- free_lofi_ioctl(klip);
- return (ENXIO);
+ err = ENXIO;
+ goto done;
} else {
lsp = ddi_get_soft_state(lofi_statep, klip->li_id);
}
if (lsp == NULL || lsp->ls_vp == NULL || lofi_access(lsp) != 0) {
- mutex_exit(&lofi_lock);
- free_lofi_ioctl(klip);
- return (ENXIO);
+ err = ENXIO;
+ goto done;
}
klip->li_id = LOFI_MINOR2ID(getminor(lsp->ls_dev));
@@ -2960,57 +3003,31 @@ lofi_unmap_file(struct lofi_ioctl *ulip, int byfilename,
*/
if (is_opened(lsp)) {
if (klip->li_force) {
+ /* Mark the device for cleanup. */
+ lofi_set_cleanup(lsp);
mutex_enter(&lsp->ls_vp_lock);
lsp->ls_vp_closereq = B_TRUE;
- /* wake up any threads waiting on dkiocstate */
+ /* Wake up any threads waiting on dkiocstate. */
cv_broadcast(&lsp->ls_vp_cv);
while (lsp->ls_vp_iocount > 0)
cv_wait(&lsp->ls_vp_cv, &lsp->ls_vp_lock);
mutex_exit(&lsp->ls_vp_lock);
-
- goto out;
} else if (klip->li_cleanup) {
- lsp->ls_cleanup = 1;
- mutex_exit(&lofi_lock);
- free_lofi_ioctl(klip);
- return (0);
+ lofi_set_cleanup(lsp);
+ } else {
+ err = EBUSY;
}
-
- mutex_exit(&lofi_lock);
- free_lofi_ioctl(klip);
- return (EBUSY);
- }
-
-out:
- lofi_free_dev(lsp);
- lofi_destroy(lsp, credp);
-
- /*
- * check the lofi_devlink_cache if device is really gone.
- * note: we just wait for timeout here and dont give error if
- * timer will expire. This check is to try to ensure the unmap is
- * really done when lofiadm -d completes.
- * Since lofi_lock is held, also hopefully the lofiadm -a calls
- * wont interfere the the unmap.
- */
- (void) snprintf(name, sizeof (name), "%d", klip->li_id);
- ticks = ddi_get_lbolt() + LOFI_TIMEOUT * drv_usectohz(1000000);
- mutex_enter(&lofi_devlink_cache.ln_lock);
- err = nvlist_lookup_nvlist(lofi_devlink_cache.ln_data, name, &nvl);
- while (err == 0) {
- err = cv_timedwait(&lofi_devlink_cache.ln_cv,
- &lofi_devlink_cache.ln_lock, ticks);
- if (err == -1)
- break;
- err = nvlist_lookup_nvlist(lofi_devlink_cache.ln_data,
- name, &nvl);
+ } else {
+ lofi_free_dev(lsp);
+ lofi_destroy(lsp, credp);
}
- mutex_exit(&lofi_devlink_cache.ln_lock);
+done:
mutex_exit(&lofi_lock);
- (void) copy_out_lofi_ioctl(klip, ulip, ioctl_flag);
+ if (err == 0)
+ (void) copy_out_lofi_ioctl(klip, ulip, ioctl_flag);
free_lofi_ioctl(klip);
- return (0);
+ return (err);
}
/*
@@ -3241,7 +3258,7 @@ lofi_ioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *credp,
mutex_enter(&lofi_lock);
lsp = ddi_get_soft_state(lofi_statep, id);
- if (lsp == NULL || lsp->ls_vp_closereq) {
+ if (lsp == NULL || lsp->ls_cleanup) {
mutex_exit(&lofi_lock);
return (ENXIO);
}
@@ -3342,27 +3359,21 @@ lofi_ioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *credp,
return (EFAULT);
mutex_enter(&lsp->ls_vp_lock);
- lsp->ls_vp_iocount++;
while (((dkstate == DKIO_INSERTED && lsp->ls_vp != NULL) ||
(dkstate == DKIO_DEV_GONE && lsp->ls_vp == NULL)) &&
- !lsp->ls_vp_closereq) {
+ !lsp->ls_cleanup) {
/*
* By virtue of having the device open, we know that
* 'lsp' will remain valid when we return.
*/
- if (!cv_wait_sig(&lsp->ls_vp_cv,
- &lsp->ls_vp_lock)) {
- lsp->ls_vp_iocount--;
- cv_broadcast(&lsp->ls_vp_cv);
+ if (!cv_wait_sig(&lsp->ls_vp_cv, &lsp->ls_vp_lock)) {
mutex_exit(&lsp->ls_vp_lock);
return (EINTR);
}
}
- dkstate = (!lsp->ls_vp_closereq && lsp->ls_vp != NULL ?
+ dkstate = (!lsp->ls_cleanup && lsp->ls_vp != NULL ?
DKIO_INSERTED : DKIO_DEV_GONE);
- lsp->ls_vp_iocount--;
- cv_broadcast(&lsp->ls_vp_cv);
mutex_exit(&lsp->ls_vp_lock);
if (ddi_copyout(&dkstate, (void *)arg,
diff --git a/usr/src/uts/common/io/net80211/net80211_amrr.c b/usr/src/uts/common/io/net80211/net80211_amrr.c
new file mode 100644
index 0000000000..61d917bfbe
--- /dev/null
+++ b/usr/src/uts/common/io/net80211/net80211_amrr.c
@@ -0,0 +1,125 @@
+/* $NetBSD: ieee80211_amrr.c,v 1.2 2007/12/11 12:40:10 lukem Exp $ */
+/* $OpenBSD: ieee80211_amrr.c,v 1.1 2006/06/17 19:07:19 damien Exp $ */
+
+/*
+ * Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+ */
+
+/*
+ * Copyright (c) 2006
+ * Damien Bergamini <damien.bergamini@free.fr>
+ *
+ * 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/param.h>
+#include <sys/socket.h>
+#include <sys/sdt.h>
+
+#include <net/if.h>
+
+#include <sys/net80211.h>
+#include <sys/net80211_amrr.h>
+#include "net80211_impl.h"
+
+#define is_success(amn) \
+ ((amn)->amn_retrycnt < (amn)->amn_txcnt / 10)
+#define is_failure(amn) \
+ ((amn)->amn_retrycnt > (amn)->amn_txcnt / 3)
+#define is_enough(amn) \
+ ((amn)->amn_txcnt > 10)
+#define is_min_rate(ni) \
+ ((ni)->in_txrate == 0)
+#define is_max_rate(ni) \
+ ((ni)->in_txrate == (ni)->in_rates.ir_nrates - 1)
+#define increase_rate(ni) \
+ ((ni)->in_txrate++)
+#define decrease_rate(ni) \
+ ((ni)->in_txrate--)
+#define reset_cnt(amn) \
+ do { (amn)->amn_txcnt = (amn)->amn_retrycnt = 0; \
+ _NOTE(CONSTCOND) } while (0)
+
+void
+ieee80211_amrr_node_init(struct ieee80211_amrr *amrr,
+ struct ieee80211_amrr_node *amn)
+{
+ amn->amn_success = 0;
+ amn->amn_recovery = 0;
+ amn->amn_txcnt = amn->amn_retrycnt = 0;
+ amn->amn_success_threshold = amrr->amrr_min_success_threshold;
+}
+
+/*
+ * Update ni->in_txrate.
+ */
+void
+ieee80211_amrr_choose(struct ieee80211_amrr *amrr, struct ieee80211_node *ni,
+ struct ieee80211_amrr_node *amn)
+{
+ int need_change = 0;
+ uint8_t rate;
+
+ if (is_success(amn) && is_enough(amn)) {
+ amn->amn_success++;
+ if (amn->amn_success >= amn->amn_success_threshold &&
+ !is_max_rate(ni)) {
+ amn->amn_recovery = 1;
+ amn->amn_success = 0;
+ increase_rate(ni);
+ rate = ni->in_rates.ir_rates[ni->in_txrate] &
+ IEEE80211_RATE_VAL;
+ ieee80211_dbg(IEEE80211_MSG_XRATE,
+ "AMRR increasing rate %d (txcnt=%d retrycnt=%d)",
+ rate, amn->amn_txcnt, amn->amn_retrycnt);
+ DTRACE_PROBE4(amrr__increase__rate,
+ struct ieee80211_node *, ni,
+ uint8_t, rate,
+ uint_t, amn->amn_txcnt,
+ uint_t, amn->amn_retrycnt);
+ need_change = 1;
+ } else {
+ amn->amn_recovery = 0;
+ }
+ } else if (is_failure(amn)) {
+ amn->amn_success = 0;
+ if (!is_min_rate(ni)) {
+ if (amn->amn_recovery) {
+ amn->amn_success_threshold *= 2;
+ if (amn->amn_success_threshold >
+ amrr->amrr_max_success_threshold)
+ amn->amn_success_threshold =
+ amrr->amrr_max_success_threshold;
+ } else {
+ amn->amn_success_threshold =
+ amrr->amrr_min_success_threshold;
+ }
+ decrease_rate(ni);
+ rate = ni->in_rates.ir_rates[ni->in_txrate] &
+ IEEE80211_RATE_VAL;
+ ieee80211_dbg(IEEE80211_MSG_XRATE,
+ "AMRR decreasing rate %d (txcnt=%d retrycnt=%d)",
+ rate, amn->amn_txcnt, amn->amn_retrycnt);
+ DTRACE_PROBE4(amrr__decrease__rate,
+ struct ieee80211_node *, ni,
+ uint8_t, rate,
+ uint_t, amn->amn_txcnt,
+ uint_t, amn->amn_retrycnt);
+ need_change = 1;
+ }
+ amn->amn_recovery = 0;
+ }
+
+ if (is_enough(amn) || need_change)
+ reset_cnt(amn);
+}
diff --git a/usr/src/uts/common/io/net80211/net80211_output.c b/usr/src/uts/common/io/net80211/net80211_output.c
index 2f68bac444..b83cf5194c 100644
--- a/usr/src/uts/common/io/net80211/net80211_output.c
+++ b/usr/src/uts/common/io/net80211/net80211_output.c
@@ -1,6 +1,8 @@
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
+ *
+ * Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
*/
/*
@@ -948,10 +950,10 @@ ieee80211_beacon_update(ieee80211com_t *ic, ieee80211_node_t *in,
* Finally, if an ACM policy is setup (in station mode) it's
* applied.
*/
+/* ARGSUSED */
int
ieee80211_classify(struct ieee80211com *ic, mblk_t *m,
struct ieee80211_node *ni)
-/* ARGSUSED */
{
int ac;
diff --git a/usr/src/uts/common/io/net80211/net80211_proto.c b/usr/src/uts/common/io/net80211/net80211_proto.c
index ebb54b6ec4..26dbf30b6f 100644
--- a/usr/src/uts/common/io/net80211/net80211_proto.c
+++ b/usr/src/uts/common/io/net80211/net80211_proto.c
@@ -4,6 +4,10 @@
*/
/*
+ * Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+ */
+
+/*
* Copyright (c) 2001 Atsushi Onoe
* Copyright (c) 2002-2008 Sam Leffler, Errno Consulting
* All rights reserved.
@@ -717,7 +721,7 @@ ieee80211_wme_updateparams(struct ieee80211com *ic)
wme->wme_update(ic);
ieee80211_dbg(IEEE80211_MSG_WME, "ieee80211_wme_updateparams(): "
- "%s: WME params updated, cap_info 0x%x\n",
+ "WME params updated, cap_info 0x%x\n",
ic->ic_opmode == IEEE80211_M_STA ?
wme->wme_wmeChanParams.cap_info :
wme->wme_bssChanParams.cap_info);
@@ -914,7 +918,8 @@ ieee80211_newstate(ieee80211com_t *ic, enum ieee80211_state nstate, int arg)
}
break;
case IEEE80211_S_ASSOC:
- ASSERT(ic->ic_opmode == IEEE80211_M_STA);
+ ASSERT(ic->ic_opmode == IEEE80211_M_STA ||
+ ic->ic_opmode == IEEE80211_M_IBSS);
switch (ostate) {
case IEEE80211_S_INIT:
case IEEE80211_S_SCAN:
diff --git a/usr/src/uts/common/os/firmload.c b/usr/src/uts/common/os/firmload.c
new file mode 100644
index 0000000000..dfa0a8e5d7
--- /dev/null
+++ b/usr/src/uts/common/os/firmload.c
@@ -0,0 +1,149 @@
+/* $NetBSD: firmload.c,v 1.19 2014/03/25 16:19:13 christos Exp $ */
+
+/*
+ * Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+ */
+
+/*
+ * Copyright (c) 2005, 2006 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jason R. Thorpe.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+/*
+ * The firmload API provides an interface for device drivers to access
+ * firmware images that must be loaded onto their devices.
+ */
+
+#include <sys/param.h>
+#include <sys/fcntl.h>
+#include <sys/systm.h>
+#include <sys/vnode.h>
+#include <sys/lwp.h>
+#include <sys/file.h>
+#include <sys/cmn_err.h>
+#include <sys/modctl.h>
+#include <sys/kobj.h>
+#include <sys/kobj_impl.h>
+
+#include <sys/firmload.h>
+
+struct firmware_handle {
+ struct _buf *fh_buf;
+ off_t fh_size;
+};
+
+static firmware_handle_t
+firmware_handle_alloc(void)
+{
+ return (kmem_alloc(sizeof (struct firmware_handle), KM_SLEEP));
+}
+
+static void
+firmware_handle_free(firmware_handle_t fh)
+{
+ kmem_free(fh, sizeof (struct firmware_handle));
+}
+
+/*
+ * firmware_open:
+ *
+ * Open a firmware image and return its handle.
+ */
+int
+firmware_open(const char *drvname, const char *imgname, firmware_handle_t *fhp)
+{
+ char *path;
+ firmware_handle_t fh;
+ int error;
+
+ if (drvname == NULL || imgname == NULL || fhp == NULL)
+ return (EINVAL);
+
+ path = kmem_asprintf("firmware/%s/%s", drvname, imgname);
+ fh = firmware_handle_alloc();
+
+ fh->fh_buf = kobj_open_path(path, 1, 0);
+ strfree(path);
+
+ if (fh->fh_buf == (struct _buf *)-1) {
+ firmware_handle_free(fh);
+ return (ENOENT);
+ }
+
+ error = kobj_get_filesize(fh->fh_buf, (uint64_t *)&fh->fh_size);
+ if (error != 0) {
+ kobj_close_file(fh->fh_buf);
+ firmware_handle_free(fh);
+ return (error);
+ }
+
+ *fhp = fh;
+ return (0);
+}
+
+/*
+ * firmware_close:
+ *
+ * Close a firmware image.
+ */
+int
+firmware_close(firmware_handle_t fh)
+{
+ if (fh != NULL) {
+ kobj_close_file(fh->fh_buf);
+ firmware_handle_free(fh);
+ }
+ return (0);
+}
+
+/*
+ * firmware_get_size:
+ *
+ * Return the total size of a firmware image.
+ */
+off_t
+firmware_get_size(firmware_handle_t fh)
+{
+ ASSERT(fh != NULL);
+ return (fh->fh_size);
+}
+
+/*
+ * firmware_read:
+ *
+ * Read data from a firmware image at the specified offset into
+ * the provided buffer.
+ */
+int
+firmware_read(firmware_handle_t fh, off_t offset, void *buf, size_t len)
+{
+ ASSERT(fh != NULL);
+ if (kobj_read_file(fh->fh_buf, buf, len, offset) == -1)
+ return (-1);
+
+ return (0);
+}
diff --git a/usr/src/uts/common/sys/Makefile b/usr/src/uts/common/sys/Makefile
index d6ff1d990e..91b1cf722e 100644
--- a/usr/src/uts/common/sys/Makefile
+++ b/usr/src/uts/common/sys/Makefile
@@ -27,6 +27,7 @@
# Copyright 2013 Saso Kiselkov. All rights reserved.
# Copyright 2015 Igor Kozhukhov <ikozhukhov@gmail.com>
# Copyright 2016 Nexenta Systems, Inc.
+# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
#
include $(SRC)/uts/Makefile.uts
@@ -51,6 +52,7 @@ i386_HDRS= \
fd_debug.h \
fdc.h \
fdmedia.h \
+ firmload.h \
mouse.h \
ucode.h
@@ -413,6 +415,7 @@ CHKHDRS= \
ndifm.h \
ndi_impldefs.h \
net80211.h \
+ net80211_amrr.h \
net80211_crypto.h \
net80211_ht.h \
net80211_proto.h \
diff --git a/usr/src/uts/common/sys/THIRDPARTYLICENSE.firmload b/usr/src/uts/common/sys/THIRDPARTYLICENSE.firmload
new file mode 100644
index 0000000000..c7ad0e790f
--- /dev/null
+++ b/usr/src/uts/common/sys/THIRDPARTYLICENSE.firmload
@@ -0,0 +1,26 @@
+Copyright (c) 2005, 2006 The NetBSD Foundation, Inc.
+All rights reserved.
+
+This code is derived from software contributed to The NetBSD Foundation
+by Jason R. Thorpe.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. 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.
+
+THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
diff --git a/usr/src/uts/common/sys/THIRDPARTYLICENSE.firmload.descrip b/usr/src/uts/common/sys/THIRDPARTYLICENSE.firmload.descrip
new file mode 100644
index 0000000000..afacaa4eaf
--- /dev/null
+++ b/usr/src/uts/common/sys/THIRDPARTYLICENSE.firmload.descrip
@@ -0,0 +1 @@
+NetBSD firmware loader API
diff --git a/usr/src/uts/common/sys/firmload.h b/usr/src/uts/common/sys/firmload.h
new file mode 100644
index 0000000000..f5ba44733d
--- /dev/null
+++ b/usr/src/uts/common/sys/firmload.h
@@ -0,0 +1,62 @@
+/* $NetBSD: firmload.h,v 1.2 2008/04/28 20:23:46 martin Exp $ */
+
+/*
+ * Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+ */
+
+/*
+ * Copyright (c) 2005, 2006 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jason R. Thorpe.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#ifndef _SYS_FIRMLOAD_H
+#define _SYS_FIRMLOAD_H
+
+#ifdef _KERNEL
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct firmware_handle *firmware_handle_t;
+
+extern int firmware_open(const char *, const char *, firmware_handle_t *);
+extern int firmware_close(firmware_handle_t);
+
+extern off_t firmware_get_size(firmware_handle_t);
+
+extern int firmware_read(firmware_handle_t, off_t, void *, size_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _KERNEL */
+
+#endif /* _SYS_FIRMLOAD_H */
diff --git a/usr/src/uts/common/sys/net80211.h b/usr/src/uts/common/sys/net80211.h
index 313b335afa..1946e0b284 100644
--- a/usr/src/uts/common/sys/net80211.h
+++ b/usr/src/uts/common/sys/net80211.h
@@ -4,6 +4,10 @@
*/
/*
+ * Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+ */
+
+/*
* Copyright (c) 2001 Atsushi Onoe
* Copyright (c) 2002-2005 Sam Leffler, Errno Consulting
* All rights reserved.
@@ -44,6 +48,7 @@
#include <sys/net80211_proto.h>
#include <sys/net80211_crypto.h>
#include <sys/net80211_ht.h>
+#include <sys/net80211_amrr.h>
#include <net/wpa.h>
/*
diff --git a/usr/src/uts/common/sys/net80211_amrr.h b/usr/src/uts/common/sys/net80211_amrr.h
new file mode 100644
index 0000000000..36bd2fcd0e
--- /dev/null
+++ b/usr/src/uts/common/sys/net80211_amrr.h
@@ -0,0 +1,73 @@
+/* $OpenBSD: ieee80211_amrr.h,v 1.3 2006/06/17 19:34:31 damien Exp $ */
+/* $NetBSD: ieee80211_amrr.h,v 1.1 2006/10/31 21:53:41 joerg Exp $ */
+
+/*
+ * Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+ */
+
+/*
+ * Copyright (c) 2006
+ * Damien Bergamini <damien.bergamini@free.fr>
+ *
+ * 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.
+ */
+#ifndef _NET80211_AMRR_H_
+#define _NET80211_AMRR_H_
+
+/*
+ * Naive implementation of the Adaptive Multi Rate Retry algorithm:
+ *
+ * "IEEE 802.11 Rate Adaptation: A Practical Approach"
+ * Mathieu Lacage, Hossein Manshaei, Thierry Turletti
+ * INRIA Sophia - Projet Planete
+ * http://www-sop.inria.fr/rapports/sophia/RR-5208.html
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ieee80211_node;
+
+/*
+ * Rate control settings.
+ */
+struct ieee80211_amrr {
+ uint_t amrr_min_success_threshold;
+ uint_t amrr_max_success_threshold;
+};
+
+#define IEEE80211_AMRR_MIN_SUCCESS_THRESHOLD 1
+#define IEEE80211_AMRR_MAX_SUCCESS_THRESHOLD 15
+
+/*
+ * Rate control state for a given node.
+ */
+struct ieee80211_amrr_node {
+ uint_t amn_success;
+ uint_t amn_recovery;
+ uint_t amn_success_threshold;
+ uint_t amn_txcnt;
+ uint_t amn_retrycnt;
+};
+
+void ieee80211_amrr_node_init(struct ieee80211_amrr *,
+ struct ieee80211_amrr_node *);
+void ieee80211_amrr_choose(struct ieee80211_amrr *, struct ieee80211_node *,
+ struct ieee80211_amrr_node *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _NET80211_AMRR_H_ */
diff --git a/usr/src/uts/intel/Makefile.intel b/usr/src/uts/intel/Makefile.intel
index a71a50d516..18d921894a 100644
--- a/usr/src/uts/intel/Makefile.intel
+++ b/usr/src/uts/intel/Makefile.intel
@@ -270,6 +270,7 @@ DRV_KMODS += ipsecesp
DRV_KMODS += ipw
DRV_KMODS += iwh
DRV_KMODS += iwi
+DRV_KMODS += iwn
DRV_KMODS += iwk
DRV_KMODS += iwp
DRV_KMODS += iwscn
diff --git a/usr/src/uts/intel/iwn/Makefile b/usr/src/uts/intel/iwn/Makefile
new file mode 100644
index 0000000000..a629462e56
--- /dev/null
+++ b/usr/src/uts/intel/iwn/Makefile
@@ -0,0 +1,93 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2016 Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+#
+
+#
+# This makefile drives the production of the iwn driver kernel module.
+#
+# intel architecture dependent
+#
+
+#
+# Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE = ../..
+
+#
+# Define firmware location & files
+#
+FWDIR = $(UTSBASE)/common/io/iwn/fw-iw
+FWFILES = iwlwifi-100-5.ucode iwlwifi-1000-3.ucode iwlwifi-105-6.ucode \
+ iwlwifi-135-6.ucode iwlwifi-2000-6.ucode iwlwifi-2030-6.ucode \
+ iwlwifi-4965-2.ucode iwlwifi-5000-2.ucode iwlwifi-5150-2.ucode \
+ iwlwifi-6000-4.ucode iwlwifi-6000g2a-5.ucode iwlwifi-6000g2b-6.ucode \
+ iwlwifi-6050-5.ucode
+
+#
+# Define the module and object file sets.
+#
+MODULE = iwn
+OBJECTS = $(IWN_OBJS:%=$(OBJS_DIR)/%)
+LINTS = $(IWN_OBJS:%.o=$(LINTS_DIR)/%.ln)
+ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE)
+ROOTFIRMWARE = $(FWFILES:%=$(ROOT_FIRMWARE_DIR)/$(MODULE)/%)
+
+#
+# Include common rules.
+#
+include $(UTSBASE)/intel/Makefile.intel
+
+#
+# Define targets
+#
+ALL_TARGET = $(BINARY) $(CONFMOD) $(ITUMOD)
+LINT_TARGET = $(MODULE).lint
+INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOTFIRMWARE)
+
+#
+# Overrides
+#
+LINTTAGS += -erroff=E_FUNC_RET_MAYBE_IGNORED2
+LINTTAGS += -erroff=E_FUNC_RET_ALWAYS_IGNOR2
+LINTTAGS += -erroff=E_INTEGER_OVERFLOW_DETECTED
+
+LDFLAGS += -dy -Nmisc/mac -Nmisc/net80211 -Ndrv/random -Ndrv/ip
+
+CERRWARN += -erroff=E_INTEGER_OVERFLOW_DETECTED
+
+#
+# Default build targets.
+#
+.KEEP_STATE:
+
+def: $(DEF_DEPS)
+
+all: $(ALL_DEPS)
+
+clean: $(CLEAN_DEPS)
+
+clobber: $(CLOBBER_DEPS)
+
+lint: $(LINT_DEPS)
+
+modlintlib: $(MODLINTLIB_DEPS)
+
+clean.lint: $(CLEAN_LINT_DEPS)
+
+install: $(INSTALL_DEPS)
+
+#
+# Include common targets.
+#
+include $(UTSBASE)/intel/Makefile.targ