Author: bschmidt
Date: Thu Jul 15 10:37:49 2010
New Revision: 210111
URL: http://svn.freebsd.org/changeset/base/210111

Log:
  Add support for firmware images in "type-length-value" format.
  
  Obtained from:        OpenBSD
  MFC after:    2 weeks

Modified:
  head/sys/dev/iwn/if_iwn.c
  head/sys/dev/iwn/if_iwnreg.h
  head/sys/dev/iwn/if_iwnvar.h

Modified: head/sys/dev/iwn/if_iwn.c
==============================================================================
--- head/sys/dev/iwn/if_iwn.c   Thu Jul 15 09:34:00 2010        (r210110)
+++ head/sys/dev/iwn/if_iwn.c   Thu Jul 15 10:37:49 2010        (r210111)
@@ -231,6 +231,10 @@ static int iwn4965_load_firmware(struct 
 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 *
        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)
 {

Modified: head/sys/dev/iwn/if_iwnreg.h
==============================================================================
--- head/sys/dev/iwn/if_iwnreg.h        Thu Jul 15 09:34:00 2010        
(r210110)
+++ head/sys/dev/iwn/if_iwnreg.h        Thu Jul 15 10:37:49 2010        
(r210111)
@@ -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.
  */

Modified: head/sys/dev/iwn/if_iwnvar.h
==============================================================================
--- head/sys/dev/iwn/if_iwnvar.h        Thu Jul 15 09:34:00 2010        
(r210110)
+++ head/sys/dev/iwn/if_iwnvar.h        Thu Jul 15 10:37:49 2010        
(r210111)
@@ -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;
_______________________________________________
svn-src-all@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to