Module Name:    src
Committed By:   christos
Date:           Fri Jun 18 21:10:23 UTC 2010

Modified Files:
        src/sys/dev/pci: if_iwn.c if_iwnreg.h if_iwnvar.h

Log Message:
Patch from Sverre Froyen to avoid panic when an ioctl happens while the
driver is initializing.


To generate a diff of this commit:
cvs rdiff -u -r1.45 -r1.46 src/sys/dev/pci/if_iwn.c
cvs rdiff -u -r1.7 -r1.8 src/sys/dev/pci/if_iwnreg.h
cvs rdiff -u -r1.10 -r1.11 src/sys/dev/pci/if_iwnvar.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/pci/if_iwn.c
diff -u src/sys/dev/pci/if_iwn.c:1.45 src/sys/dev/pci/if_iwn.c:1.46
--- src/sys/dev/pci/if_iwn.c:1.45	Wed May 12 08:26:16 2010
+++ src/sys/dev/pci/if_iwn.c	Fri Jun 18 17:10:23 2010
@@ -1,5 +1,5 @@
-/*	$NetBSD: if_iwn.c,v 1.45 2010/05/12 12:26:16 christos Exp $	*/
-/*	$OpenBSD: if_iwn.c,v 1.88 2010/04/10 08:37:36 damien Exp $	*/
+/*	$NetBSD: if_iwn.c,v 1.46 2010/06/18 21:10:23 christos Exp $	*/
+/*	$OpenBSD: if_iwn.c,v 1.96 2010/05/13 09:25:03 damien Exp $	*/
 
 /*-
  * Copyright (c) 2007-2010 Damien Bergamini <damien.bergam...@free.fr>
@@ -22,7 +22,7 @@
  * adapters.
  */
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: if_iwn.c,v 1.45 2010/05/12 12:26:16 christos Exp $");
+__KERNEL_RCSID(0, "$NetBSD: if_iwn.c,v 1.46 2010/06/18 21:10:23 christos Exp $");
 
 #define IWN_USE_RBUF	/* Use local storage for RX */
 #undef IWN_HWCRYPTO	/* XXX does not even compile yet */
@@ -32,7 +32,7 @@
 
 #include <sys/param.h>
 #include <sys/sockio.h>
-#include <sys/sysctl.h>
+#include <sys/proc.h>
 #include <sys/mbuf.h>
 #include <sys/kernel.h>
 #include <sys/socket.h>
@@ -96,6 +96,19 @@
 	PCI_PRODUCT_INTEL_WIFI_LINK_6050_2X2_2,
 	PCI_PRODUCT_INTEL_WIFI_LINK_6005_2X2_1,
 	PCI_PRODUCT_INTEL_WIFI_LINK_6005_2X2_2,
+#ifdef notyet
+	/*
+	 * XXX NetBSD: the 6005A replaces the two 6005, above
+	 * (see OpenBSD rev 1.96).
+	 */
+	PCI_PRODUCT_INTEL_WIFI_LINK_6005A_2X2_1,
+	PCI_PRODUCT_INTEL_WIFI_LINK_6005A_2X2_2,
+	PCI_PRODUCT_INTEL_WIFI_LINK_6005B_1X1_1,
+	PCI_PRODUCT_INTEL_WIFI_LINK_6005B_1X1_2,
+	PCI_PRODUCT_INTEL_WIFI_LINK_6005B_2X2_1,
+	PCI_PRODUCT_INTEL_WIFI_LINK_6005B_2X2_2,
+	PCI_PRODUCT_INTEL_WIFI_LINK_6005B_2X2_3
+#endif
 };
 
 /*
@@ -263,6 +276,10 @@
 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	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	iwn5000_load_firmware(struct iwn_softc *);
 static int	iwn_read_firmware(struct iwn_softc *);
 static int	iwn_clock_wait(struct iwn_softc *);
@@ -3144,6 +3161,22 @@
 	int s, error = 0;
 
 	s = splnet();
+	/*
+	 * Prevent processes from entering this function while another
+	 * process is tsleep'ing in it.
+	 */
+	if (sc->sc_flags & IWN_FLAG_BUSY) {
+		switch (cmd) {
+		case SIOCSIFADDR:
+			/* FALLTHROUGH */
+		case SIOCSIFFLAGS:
+			splx(s);
+			aprint_normal_dev(sc->sc_dev,
+			    "ioctl while busy cmd = 0x%lx\n", cmd);
+			return EBUSY;
+		}
+	}
+	sc->sc_flags |= IWN_FLAG_BUSY;
 
 	switch (cmd) {
 	case SIOCSIFADDR:
@@ -3190,6 +3223,8 @@
 			error = iwn_init(ifp);
 		}
 	}
+
+	sc->sc_flags &= ~IWN_FLAG_BUSY;
 	splx(s);
 	return error;
 }
@@ -4296,7 +4331,7 @@
 	tx->lifetime = htole32(IWN_LIFETIME_INFINITE);
 
 	if (flags & IEEE80211_CHAN_5GHZ) {
-		hdr->crc_threshold = htole16(1);
+		hdr->crc_threshold = 0xffff;
 		/* Send probe requests at 6Mbps. */
 		tx->plcp = iwn_rates[IWN_RIDX_OFDM6].plcp;
 		rs = &ic->ic_sup_rates[IEEE80211_MODE_11A];
@@ -4334,6 +4369,10 @@
 	frm = (uint8_t *)(wh + 1);
 	frm = ieee80211_add_ssid(frm, NULL, 0);
 	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->rs_nrates > IEEE80211_RATE_SIZE)
 		frm = ieee80211_add_xrates(frm, rs);
 
@@ -5242,20 +5281,155 @@
 	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)
+{
+	const uint32_t *ptr;
+	size_t hdrlen = 24;
+	uint32_t rev;
+
+	ptr = (const uint32_t *)fw->data;
+	rev = le32toh(*ptr++);
+
+	/* Check firmware API version. */
+	if (IWN_FW_API(rev) <= 1) {
+		aprint_error_dev(sc->sc_dev,
+		    "bad firmware, need API version >=2\n");
+		return EINVAL;
+	}
+	if (IWN_FW_API(rev) >= 3) {
+		/* Skip build number (version 2 header). */
+		hdrlen += 4;
+		ptr++;
+	}
+	if (fw->size < hdrlen) {
+		aprint_error_dev(sc->sc_dev,
+		    "firmware too short: %zd bytes\n", 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) {
+		aprint_error_dev(sc->sc_dev,
+		    "firmware too short: %zd bytes\n", 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)
+{
+	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)) {
+		aprint_error_dev(sc->sc_dev,
+		    "firmware too short: %zd bytes\n", fw->size);
+		return EINVAL;
+	}
+	hdr = (const struct iwn_fw_tlv_hdr *)fw->data;
+	if (hdr->signature != htole32(IWN_FW_SIGNATURE)) {
+		aprint_error_dev(sc->sc_dev,
+		    "bad firmware signature 0x%08x\n", le32toh(hdr->signature));
+		return EINVAL;
+	}
+	DPRINTF(("FW: \"%.64s\", build 0x%x\n", hdr->descr,
+	    le32toh(hdr->build)));
+
+	/*
+	 * 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. */
+	DPRINTF(("using alternative %d\n", 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) {
+			aprint_error_dev(sc->sc_dev,
+			    "firmware too short: %zd bytes\n", fw->size);
+			return EINVAL;
+		}
+		/* Skip other alternatives. */
+		if (tlv->alt != 0 && tlv->alt != htole16(alt))
+			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;
+		default:
+			DPRINTF(("TLV type %d not handled\n",
+			    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)
 {
 	const struct iwn_hal *hal = sc->sc_hal;
 	struct iwn_fw_info *fw = &sc->fw;
 	firmware_handle_t fwh;
-	const uint32_t *ptr;
-	uint32_t rev;
-	size_t size;
 	int error;
 
 	/* Initialize for error returns */
 	fw->data = NULL;
-	fw->datasz = 0;
+	fw->size = 0;
 
 	/* Open firmware image. */
 	if ((error = firmware_open("if_iwn", sc->fwname, &fwh)) != 0) {
@@ -5263,53 +5437,42 @@
 		    "could not get firmware handle %s\n", sc->fwname);
 		return error;
 	}
-	size = firmware_get_size(fwh);
-	if (size < 28) {
+	fw->size = firmware_get_size(fwh);
+	if (fw->size < sizeof (uint32_t)) {
 		aprint_error_dev(sc->sc_dev,
-		    "truncated firmware header: %zu bytes\n", size);
+		    "firmware too short: %zd bytes\n", fw->size);
 		firmware_close(fwh);
 		return EINVAL;
 	}
 
 	/* Read the firmware. */
-	fw->data = firmware_malloc(size);
+	fw->data = firmware_malloc(fw->size);
 	if (fw->data == NULL) {
 		aprint_error_dev(sc->sc_dev,
 		    "not enough memory to stock firmware %s\n", sc->fwname);
 		firmware_close(fwh);
 		return ENOMEM;
 	}
-	error = firmware_read(fwh, 0, fw->data, size);
+	error = firmware_read(fwh, 0, fw->data, fw->size);
 	firmware_close(fwh);
-	fw->datasz = size;
 	if (error != 0) {
 		aprint_error_dev(sc->sc_dev,
 		    "could not read firmware %s\n", sc->fwname);
 		goto out;
 	}
 
-	/* Process firmware header. */
-	ptr = (const uint32_t *)fw->data;
-	rev = le32toh(*ptr++);
-	/* Check firmware API version. */
-	if (IWN_FW_API(rev) <= 1) {
+	/* Retrieve text and data sections. */
+	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) {
 		aprint_error_dev(sc->sc_dev,
-		    "bad firmware, need API version >=2\n");
+		    "could not read firmware sections\n");
 		goto out;
 	}
-	if (IWN_FW_API(rev) >= 3) {
-		/* Skip build number (version 2 header). */
-		size -= 4;
-		ptr++;
-	}
-	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++);
-	size -= 24;
 
-	/* Sanity-check firmware header. */
+	/* Make sure text and data sections fit in hardware memory. */
 	if (fw->main.textsz > hal->fw_text_maxsz ||
 	    fw->main.datasz > hal->fw_data_maxsz ||
 	    fw->init.textsz > hal->fw_text_maxsz ||
@@ -5317,30 +5480,16 @@
 	    fw->boot.textsz > IWN_FW_BOOT_TEXT_MAXSZ ||
 	    (fw->boot.textsz & 3) != 0) {
 		aprint_error_dev(sc->sc_dev,
-		    "invalid firmware header\n");
-		goto out;
-	}
-
-	/* Check that all firmware sections fit. */
-	if (fw->main.textsz + fw->main.datasz + fw->init.textsz +
-	    fw->init.datasz + fw->boot.textsz > size) {
-		aprint_error_dev(sc->sc_dev,
-		    "firmware file too short: %zu bytes\n", size);
+		    "firmware sections too large\n");
 		goto out;
 	}
 
-	/* 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;
-
+	/* We can proceed with loading the firmware. */
 	return 0;
 out:
-	firmware_free(fw->data, fw->datasz);
+	firmware_free(fw->data, fw->size);
 	fw->data = NULL;
-	fw->datasz = 0;
+	fw->size = 0;
 	return error ? error : EINVAL;
 }
 
@@ -5733,11 +5882,11 @@
 	sc->sc_flags &= ~IWN_FLAG_USE_ICT;
 
 	/* Initialize hardware and upload firmware. */
-	KASSERT(sc->fw.data != NULL && sc->fw.datasz > 0);
+	KASSERT(sc->fw.data != NULL && sc->fw.size > 0);
 	error = iwn_hw_init(sc);
-	firmware_free(sc->fw.data, sc->fw.datasz);
+	firmware_free(sc->fw.data, sc->fw.size);
 	sc->fw.data = NULL;
-	sc->fw.datasz = 0;
+	sc->fw.size = 0;
 	if (error != 0) {
 		aprint_error_dev(sc->sc_dev,
 		    "could not initialize hardware\n");

Index: src/sys/dev/pci/if_iwnreg.h
diff -u src/sys/dev/pci/if_iwnreg.h:1.7 src/sys/dev/pci/if_iwnreg.h:1.8
--- src/sys/dev/pci/if_iwnreg.h:1.7	Thu Apr 15 21:40:41 2010
+++ src/sys/dev/pci/if_iwnreg.h	Fri Jun 18 17:10:23 2010
@@ -1,5 +1,5 @@
-/*	$NetBSD: if_iwnreg.h,v 1.7 2010/04/16 01:40:41 christos Exp $	*/
-/*	$OpenBSD: if_iwnreg.h,v 1.38 2010/04/10 08:37:36 damien Exp $	*/
+/*	$NetBSD: if_iwnreg.h,v 1.8 2010/06/18 21:10:23 christos Exp $	*/
+/*	$OpenBSD: if_iwnreg.h,v 1.40 2010/05/05 19:41:57 damien Exp $	*/
 
 /*-
  * Copyright (c) 2007, 2008
@@ -1253,6 +1253,34 @@
 	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
+
+	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)
@@ -1261,8 +1289,6 @@
 #define IWN4965_FWSZ		(IWN4965_FW_TEXT_MAXSZ + IWN4965_FW_DATA_MAXSZ)
 #define IWN5000_FWSZ		IWN5000_FW_TEXT_MAXSZ
 
-#define IWN_FW_API(x)	(((x) >> 8) & 0xff)
-
 /*
  * Offsets into EEPROM.
  */

Index: src/sys/dev/pci/if_iwnvar.h
diff -u src/sys/dev/pci/if_iwnvar.h:1.10 src/sys/dev/pci/if_iwnvar.h:1.11
--- src/sys/dev/pci/if_iwnvar.h:1.10	Fri Apr 23 16:56:20 2010
+++ src/sys/dev/pci/if_iwnvar.h	Fri Jun 18 17:10:23 2010
@@ -1,5 +1,5 @@
-/*	$NetBSD: if_iwnvar.h,v 1.10 2010/04/23 20:56:20 christos Exp $	*/
-/*	$OpenBSD: if_iwnvar.h,v 1.17 2010/02/17 18:23:00 damien Exp $	*/
+/*	$NetBSD: if_iwnvar.h,v 1.11 2010/06/18 21:10:23 christos Exp $	*/
+/*	$OpenBSD: if_iwnvar.h,v 1.19 2010/05/05 19:47:43 damien Exp $	*/
 
 /*-
  * Copyright (c) 2007, 2008
@@ -168,7 +168,7 @@
 
 struct iwn_fw_info {
 	u_char			*data;
-	uint32_t		datasz;
+	size_t			size;
 	struct iwn_fw_part	init;
 	struct iwn_fw_part	main;
 	struct iwn_fw_part	boot;
@@ -226,6 +226,7 @@
 #define IWN_FLAG_CALIB_DONE	(1 << 2)
 #define IWN_FLAG_USE_ICT	(1 << 3)
 #define IWN_FLAG_INTERNAL_PA	(1 << 4)
+#define IWN_FLAG_BUSY		(1 << 5)
 /* Added for NetBSD */
 #define IWN_FLAG_SCANNING	(1 << 8)
 

Reply via email to