summaryrefslogtreecommitdiffstats
path: root/sys/dev/iwn
diff options
context:
space:
mode:
authorbschmidt <bschmidt@FreeBSD.org>2010-07-15 10:37:49 +0000
committerbschmidt <bschmidt@FreeBSD.org>2010-07-15 10:37:49 +0000
commitf67e656794cfb03ecc8df0db2ee47df236537927 (patch)
tree24f81fcae98b57bc4be7b93bc25444f48159ee15 /sys/dev/iwn
parentc7180de9b584cc2c7b903ecccaa7958d395efe31 (diff)
downloadFreeBSD-src-f67e656794cfb03ecc8df0db2ee47df236537927.zip
FreeBSD-src-f67e656794cfb03ecc8df0db2ee47df236537927.tar.gz
Add support for firmware images in "type-length-value" format.
Obtained from: OpenBSD MFC after: 2 weeks
Diffstat (limited to 'sys/dev/iwn')
-rw-r--r--sys/dev/iwn/if_iwn.c208
-rw-r--r--sys/dev/iwn/if_iwnreg.h32
-rw-r--r--sys/dev/iwn/if_iwnvar.h5
3 files changed, 197 insertions, 48 deletions
diff --git a/sys/dev/iwn/if_iwn.c b/sys/dev/iwn/if_iwn.c
index 4dc0352..5b348bd 100644
--- a/sys/dev/iwn/if_iwn.c
+++ b/sys/dev/iwn/if_iwn.c
@@ -231,6 +231,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 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 *);
@@ -5644,39 +5648,19 @@ iwn5000_load_firmware(struct iwn_softc *sc)
return 0;
}
+/*
+ * Extract text and data sections from a legacy firmware image.
+ */
static int
-iwn_read_firmware(struct iwn_softc *sc)
+iwn_read_firmware_leg(struct iwn_softc *sc, struct iwn_fw_info *fw)
{
- const struct iwn_hal *hal = sc->sc_hal;
- struct iwn_fw_info *fw = &sc->fw;
const uint32_t *ptr;
+ size_t hdrlen = 24;
uint32_t rev;
- size_t size;
-
- IWN_UNLOCK(sc);
- /* Read firmware image from filesystem. */
- sc->fw_fp = firmware_get(sc->fwname);
- if (sc->fw_fp == NULL) {
- device_printf(sc->sc_dev,
- "%s: could not load firmare image \"%s\"\n", __func__,
- sc->fwname);
- IWN_LOCK(sc);
- return EINVAL;
- }
- IWN_LOCK(sc);
-
- size = sc->fw_fp->datasize;
- if (size < 28) {
- device_printf(sc->sc_dev,
- "%s: truncated firmware header: %zu bytes\n",
- __func__, size);
- return EINVAL;
- }
-
- /* Process firmware header. */
ptr = (const uint32_t *)sc->fw_fp->data;
rev = le32toh(*ptr++);
+
/* Check firmware API version. */
if (IWN_FW_API(rev) <= 1) {
device_printf(sc->sc_dev,
@@ -5685,34 +5669,27 @@ iwn_read_firmware(struct iwn_softc *sc)
}
if (IWN_FW_API(rev) >= 3) {
/* Skip build number (version 2 header). */
- size -= 4;
+ hdrlen += 4;
ptr++;
}
+ if (fw->size < hdrlen) {
+ device_printf(sc->sc_dev,
+ "%s: firmware file too short: %zu bytes\n",
+ __func__, 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++);
- size -= 24;
-
- /* Sanity-check firmware header. */
- if (fw->main.textsz > hal->fw_text_maxsz ||
- fw->main.datasz > hal->fw_data_maxsz ||
- fw->init.textsz > hal->fw_text_maxsz ||
- fw->init.datasz > hal->fw_data_maxsz ||
- fw->boot.textsz > IWN_FW_BOOT_TEXT_MAXSZ ||
- (fw->boot.textsz & 3) != 0) {
- device_printf(sc->sc_dev, "%s: invalid firmware header\n",
- __func__);
- return EINVAL;
- }
/* Check that all firmware sections fit. */
- if (fw->main.textsz + fw->main.datasz + fw->init.textsz +
- fw->init.datasz + fw->boot.textsz > size) {
+ if (fw->size < hdrlen + fw->main.textsz + fw->main.datasz +
+ fw->init.textsz + fw->init.datasz + fw->boot.textsz) {
device_printf(sc->sc_dev,
"%s: firmware file too short: %zu bytes\n",
- __func__, size);
+ __func__, fw->size);
return EINVAL;
}
@@ -5726,6 +5703,151 @@ iwn_read_firmware(struct iwn_softc *sc)
return 0;
}
+/*
+ * Extract text and data sections from a TLV firmware image.
+ */
+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)) {
+ device_printf(sc->sc_dev,
+ "%s: firmware file too short: %zu bytes\n",
+ __func__, fw->size);
+ return EINVAL;
+ }
+ hdr = (const struct iwn_fw_tlv_hdr *)fw->data;
+ if (hdr->signature != htole32(IWN_FW_SIGNATURE)) {
+ device_printf(sc->sc_dev,
+ "%s: bad firmware file signature 0x%08x\n",
+ __func__, 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. */
+
+ 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) {
+ device_printf(sc->sc_dev,
+ "%s: firmware file too short: %zu bytes\n",
+ __func__, 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(sc, IWN_DEBUG_RESET,
+ "%s: TLV type %d not handled\n",
+ __func__, 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;
+ int error;
+
+ IWN_UNLOCK(sc);
+
+ memset(fw, 0, sizeof (*fw));
+
+ /* Read firmware image from filesystem. */
+ sc->fw_fp = firmware_get(sc->fwname);
+ if (sc->fw_fp == NULL) {
+ device_printf(sc->sc_dev,
+ "%s: could not load firmare image \"%s\"\n", __func__,
+ sc->fwname);
+ IWN_LOCK(sc);
+ return EINVAL;
+ }
+ IWN_LOCK(sc);
+
+ fw->size = sc->fw_fp->datasize;
+ fw->data = (const uint8_t *)sc->fw_fp->data;
+ if (fw->size < sizeof (uint32_t)) {
+ device_printf(sc->sc_dev,
+ "%s: firmware file too short: %zu bytes\n",
+ __func__, fw->size);
+ return EINVAL;
+ }
+
+ /* 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) {
+ device_printf(sc->sc_dev,
+ "%s: could not read firmware sections\n", __func__);
+ return error;
+ }
+
+ /* 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 ||
+ fw->init.datasz > hal->fw_data_maxsz ||
+ fw->boot.textsz > IWN_FW_BOOT_TEXT_MAXSZ ||
+ (fw->boot.textsz & 3) != 0) {
+ device_printf(sc->sc_dev,
+ "%s: firmware sections too large\n", __func__);
+ return EINVAL;
+ }
+
+ /* We can proceed with loading the firmware. */
+ return 0;
+}
+
static int
iwn_clock_wait(struct iwn_softc *sc)
{
diff --git a/sys/dev/iwn/if_iwnreg.h b/sys/dev/iwn/if_iwnreg.h
index 8829210..c023009 100644
--- a/sys/dev/iwn/if_iwnreg.h
+++ b/sys/dev/iwn/if_iwnreg.h
@@ -1,5 +1,5 @@
/* $FreeBSD$ */
-/* $OpenBSD: if_iwnreg.h,v 1.38 2010/04/10 08:37:36 damien Exp $ */
+/* $OpenBSD: if_iwnreg.h,v 1.40 2010/05/05 19:41:57 damien Exp $ */
/*-
* Copyright (c) 2007, 2008
@@ -1260,6 +1260,34 @@ struct iwn_fw_dump {
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)
@@ -1268,8 +1296,6 @@ struct iwn_fw_dump {
#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.
*/
diff --git a/sys/dev/iwn/if_iwnvar.h b/sys/dev/iwn/if_iwnvar.h
index 855388c..90a8d95 100644
--- a/sys/dev/iwn/if_iwnvar.h
+++ b/sys/dev/iwn/if_iwnvar.h
@@ -1,5 +1,5 @@
/* $FreeBSD$ */
-/* $OpenBSD: if_iwnvar.h,v 1.17 2010/02/17 18:23:00 damien Exp $ */
+/* $OpenBSD: if_iwnvar.h,v 1.18 2010/04/30 16:06:46 damien Exp $ */
/*-
* Copyright (c) 2007, 2008
@@ -150,7 +150,8 @@ struct iwn_fw_part {
};
struct iwn_fw_info {
- u_char *data;
+ const uint8_t *data;
+ size_t size;
struct iwn_fw_part init;
struct iwn_fw_part main;
struct iwn_fw_part boot;
OpenPOWER on IntegriCloud