summaryrefslogtreecommitdiff
path: root/sys/dev/pci/if_iwn.c
diff options
context:
space:
mode:
authorDamien Bergamini <damien@cvs.openbsd.org>2010-04-30 16:06:47 +0000
committerDamien Bergamini <damien@cvs.openbsd.org>2010-04-30 16:06:47 +0000
commit34644739c1a40c2f7194a8d776349a74ee4da7da (patch)
treec3dd186dfd7dd33942fb6711b3bfa6747e06c8c2 /sys/dev/pci/if_iwn.c
parent8b114d886ea016b76c7e9ecddecd7506884a7c06 (diff)
add support for firmware images in "type-length-value" format.
Diffstat (limited to 'sys/dev/pci/if_iwn.c')
-rw-r--r--sys/dev/pci/if_iwn.c178
1 files changed, 138 insertions, 40 deletions
diff --git a/sys/dev/pci/if_iwn.c b/sys/dev/pci/if_iwn.c
index d9247671e84..554fa39e2f0 100644
--- a/sys/dev/pci/if_iwn.c
+++ b/sys/dev/pci/if_iwn.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: if_iwn.c,v 1.89 2010/04/20 22:05:43 tedu Exp $ */
+/* $OpenBSD: if_iwn.c,v 1.90 2010/04/30 16:06:46 damien Exp $ */
/*-
* Copyright (c) 2007-2010 Damien Bergamini <damien.bergamini@free.fr>
@@ -237,6 +237,10 @@ int iwn4965_load_firmware(struct iwn_softc *);
int iwn5000_load_firmware_section(struct iwn_softc *, uint32_t,
const uint8_t *, int);
int iwn5000_load_firmware(struct iwn_softc *);
+int iwn_read_firmware_leg(struct iwn_softc *,
+ struct iwn_fw_info *);
+int iwn_read_firmware_tlv(struct iwn_softc *,
+ struct iwn_fw_info *);
int iwn_read_firmware(struct iwn_softc *);
int iwn_clock_wait(struct iwn_softc *);
int iwn_apm_init(struct iwn_softc *);
@@ -5155,69 +5159,46 @@ iwn5000_load_firmware(struct iwn_softc *sc)
return 0;
}
+/*
+ * Extract text and data sections from a legacy firmware image.
+ */
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;
- int error;
- /* Read firmware image from filesystem. */
- if ((error = loadfirmware(sc->fwname, &fw->data, &size)) != 0) {
- printf("%s: error, %d, could not read firmware %s\n",
- sc->sc_dev.dv_xname, error, sc->fwname);
- return error;
- }
- if (size < 28) {
- printf("%s: truncated firmware header: %d bytes\n",
- sc->sc_dev.dv_xname, size);
- free(fw->data, M_DEVBUF);
- return EINVAL;
- }
-
- /* Process firmware header. */
ptr = (const uint32_t *)fw->data;
rev = letoh32(*ptr++);
+
/* Check firmware API version. */
if (IWN_FW_API(rev) <= 1) {
printf("%s: bad firmware, need API version >=2\n",
sc->sc_dev.dv_xname);
- free(fw->data, M_DEVBUF);
return EINVAL;
}
if (IWN_FW_API(rev) >= 3) {
/* Skip build number (version 2 header). */
- size -= 4;
+ hdrlen += 4;
ptr++;
}
+ if (fw->size < hdrlen) {
+ printf("%s: firmware too short: %d bytes\n",
+ sc->sc_dev.dv_xname, fw->size);
+ return EINVAL;
+ }
fw->main.textsz = letoh32(*ptr++);
fw->main.datasz = letoh32(*ptr++);
fw->init.textsz = letoh32(*ptr++);
fw->init.datasz = letoh32(*ptr++);
fw->boot.textsz = letoh32(*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) {
- printf("%s: invalid firmware header\n", sc->sc_dev.dv_xname);
- free(fw->data, M_DEVBUF);
- 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) {
- printf("%s: firmware file too short: %d bytes\n",
- sc->sc_dev.dv_xname, size);
- free(fw->data, M_DEVBUF);
+ if (fw->size < hdrlen + fw->main.textsz + fw->main.datasz +
+ fw->init.textsz + fw->init.datasz + fw->boot.textsz) {
+ printf("%s: firmware too short: %d bytes\n",
+ sc->sc_dev.dv_xname, fw->size);
return EINVAL;
}
@@ -5227,7 +5208,124 @@ iwn_read_firmware(struct iwn_softc *sc)
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.
+ */
+int
+iwn_read_firmware_tlv(struct iwn_softc *sc, struct iwn_fw_info *fw)
+{
+ const struct iwn_fw_tlv_hdr *hdr;
+ const uint32_t *ptr, *end;
+ uint32_t type, len;
+
+ if (fw->size < sizeof (*hdr)) {
+ printf("%s: firmware too short: %d bytes\n",
+ sc->sc_dev.dv_xname, fw->size);
+ return EINVAL;
+ }
+ hdr = (const struct iwn_fw_tlv_hdr *)fw->data;
+ if (hdr->signature != htole32(IWN_FW_SIGNATURE)) {
+ printf("%s: bad firmware signature 0x%08x\n",
+ sc->sc_dev.dv_xname, letoh32(hdr->signature));
+ return EINVAL;
+ }
+ DPRINTF(("FW: \"%.64s\", build 0x%x\n", hdr->descr,
+ letoh32(hdr->build)));
+
+ ptr = (const uint32_t *)(hdr + 1);
+ end = (const uint32_t *)(fw->data + fw->size);
+
+ /* Parse type-length-value fields. */
+ while (ptr + 2 <= end) {
+ type = letoh32(*ptr++);
+ len = letoh32(*ptr++);
+ if (ptr + (len + 3) / 4 > end) {
+ printf("%s: firmware too short: %d bytes\n",
+ sc->sc_dev.dv_xname, fw->size);
+ return EINVAL;
+ }
+ switch (type) {
+ case IWN_FW_TLV_MAIN_TEXT:
+ fw->main.text = (const uint8_t *)ptr;
+ fw->main.textsz = len;
+ break;
+ case IWN_FW_TLV_MAIN_DATA:
+ fw->main.data = (const uint8_t *)ptr;
+ fw->main.datasz = len;
+ break;
+ case IWN_FW_TLV_INIT_TEXT:
+ fw->init.text = (const uint8_t *)ptr;
+ fw->init.textsz = len;
+ break;
+ case IWN_FW_TLV_INIT_DATA:
+ fw->init.data = (const uint8_t *)ptr;
+ fw->init.datasz = len;
+ break;
+ case IWN_FW_TLV_BOOT_TEXT:
+ fw->boot.text = (const uint8_t *)ptr;
+ fw->boot.textsz = len;
+ break;
+ default:
+ DPRINTF(("TLV type %d not handled\n", type));
+ break;
+ }
+ /* TLV fields are 32-bit aligned. */
+ ptr += (len + 3) / 4;
+ }
+ return 0;
+}
+
+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;
+
+ memset(fw, 0, sizeof (*fw));
+
+ /* Read firmware image from filesystem. */
+ if ((error = loadfirmware(sc->fwname, &fw->data, &fw->size)) != 0) {
+ printf("%s: error, %d, could not read firmware %s\n",
+ sc->sc_dev.dv_xname, error, sc->fwname);
+ return error;
+ }
+ if (fw->size < sizeof (uint32_t)) {
+ printf("%s: firmware too short: %d bytes\n",
+ sc->sc_dev.dv_xname, fw->size);
+ free(fw->data, M_DEVBUF);
+ 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);
+ if (error != 0) {
+ printf("%s: could not read firmware sections\n",
+ sc->sc_dev.dv_xname);
+ free(fw->data, M_DEVBUF);
+ 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) {
+ printf("%s: firmware sections too large\n",
+ sc->sc_dev.dv_xname);
+ free(fw->data, M_DEVBUF);
+ return EINVAL;
+ }
+ /* We can proceed with loading the firmware. */
return 0;
}