int
iwn_load_firmware(struct iwn_softc *sc)
{
	int error;

	KASSERT(sc->fw_fp == NULL, ("firmware already loaded"));

	IWN_UNLOCK(sc);
	/* load firmware image from disk */
	sc->fw_fp = firmware_get("iwnfw");
	if (sc->fw_fp == NULL) {
		device_printf(sc->sc_dev,
		    "%s: could not load firmare image \"iwnfw\"\n", __func__);
		error = EINVAL;
	} else
		error = 0;
	IWN_LOCK(sc);
	return error;
}

int
iwn_transfer_firmware(struct iwn_softc *sc)
{
	struct iwn_dma_info *dma = &sc->fw_dma;
	const struct iwn_firmware_hdr *hdr;
	const uint8_t *init_text, *init_data, *main_text, *main_data;
	const uint8_t *boot_text;
	uint32_t init_textsz, init_datasz, main_textsz, main_datasz;
	uint32_t boot_textsz;
	int error = 0;
	const struct firmware *fp = sc->fw_fp;

	/* extract firmware header information */
	if (fp->datasize < sizeof (struct iwn_firmware_hdr)) {
		device_printf(sc->sc_dev,
		    "%s: truncated firmware header: %zu bytes, expecting %zu\n",
		    __func__, fp->datasize, sizeof (struct iwn_firmware_hdr));
		error = EINVAL;
		goto fail;
	}
	hdr = (const struct iwn_firmware_hdr *)fp->data;
	main_textsz = le32toh(hdr->main_textsz);
	main_datasz = le32toh(hdr->main_datasz);
	init_textsz = le32toh(hdr->init_textsz);
	init_datasz = le32toh(hdr->init_datasz);
	boot_textsz = le32toh(hdr->boot_textsz);

	/* sanity-check firmware segments sizes */
	if (main_textsz > IWN_FW_MAIN_TEXT_MAXSZ ||
	    main_datasz > IWN_FW_MAIN_DATA_MAXSZ ||
	    init_textsz > IWN_FW_INIT_TEXT_MAXSZ ||
	    init_datasz > IWN_FW_INIT_DATA_MAXSZ ||
	    boot_textsz > IWN_FW_BOOT_TEXT_MAXSZ ||
	    (boot_textsz & 3) != 0) {
		device_printf(sc->sc_dev,
		    "%s: invalid firmware header, main [%d,%d], init [%d,%d] "
		    "boot %d\n", __func__, main_textsz, main_datasz,
		    init_textsz, init_datasz, boot_textsz);
		error = EINVAL;
		goto fail;
	}

	/* check that all firmware segments are present */
	if (fp->datasize < sizeof (struct iwn_firmware_hdr) + main_textsz +
	    main_datasz + init_textsz + init_datasz + boot_textsz) {
		device_printf(sc->sc_dev, "%s: firmware file too short: "
		    "%zu bytes, main [%d, %d], init [%d,%d] boot %d\n",
		    __func__, fp->datasize, main_textsz, main_datasz,
		    init_textsz, init_datasz, boot_textsz);
		error = EINVAL;
		goto fail;
	}

	/* get pointers to firmware segments */
	main_text = (const uint8_t *)(hdr + 1);
	main_data = main_text + main_textsz;
	init_text = main_data + main_datasz;
	init_data = init_text + init_textsz;
	boot_text = init_data + init_datasz;

	/* copy initialization images into pre-allocated DMA-safe memory */
	memcpy(dma->vaddr, init_data, init_datasz);
	memcpy(dma->vaddr + IWN_FW_INIT_DATA_MAXSZ, init_text, init_textsz);

	/* tell adapter where to find initialization images */
	iwn_mem_lock(sc);
	iwn_mem_write(sc, IWN_MEM_DATA_BASE, dma->paddr >> 4);
	iwn_mem_write(sc, IWN_MEM_DATA_SIZE, init_datasz);
	iwn_mem_write(sc, IWN_MEM_TEXT_BASE,
	    (dma->paddr + IWN_FW_INIT_DATA_MAXSZ) >> 4);
	iwn_mem_write(sc, IWN_MEM_TEXT_SIZE, init_textsz);
	iwn_mem_unlock(sc);

	/* load firmware boot code */
	error = iwn_transfer_microcode(sc, boot_text, boot_textsz);
	if (error != 0) {
		device_printf(sc->sc_dev,
		    "%s: could not load boot firmware, error %d\n",
		    __func__, error);
		goto fail;
	}

	/* now press "execute" ;-) */
	IWN_WRITE(sc, IWN_RESET, 0);

	/* wait at most one second for first alive notification */
	error = msleep(sc, &sc->sc_mtx, PCATCH, "iwninit", hz);
	if (error != 0) {
		/* this isn't what was supposed to happen.. */
		device_printf(sc->sc_dev,
		    "%s: timeout waiting for first alive notice, error %d\n",
		    __func__, error);
		goto fail;
	}

	/* copy runtime images into pre-allocated DMA-safe memory */
	memcpy(dma->vaddr, main_data, main_datasz);
	memcpy(dma->vaddr + IWN_FW_MAIN_DATA_MAXSZ, main_text, main_textsz);

	/* tell adapter where to find runtime images */
	iwn_mem_lock(sc);
	iwn_mem_write(sc, IWN_MEM_DATA_BASE, dma->paddr >> 4);
	iwn_mem_write(sc, IWN_MEM_DATA_SIZE, main_datasz);
	iwn_mem_write(sc, IWN_MEM_TEXT_BASE,
	    (dma->paddr + IWN_FW_MAIN_DATA_MAXSZ) >> 4);
	iwn_mem_write(sc, IWN_MEM_TEXT_SIZE, IWN_FW_UPDATED | main_textsz);
	iwn_mem_unlock(sc);

	/* wait at most one second for second alive notification */
	error = msleep(sc, &sc->sc_mtx, PCATCH, "iwninit", hz);
	if (error != 0) {
		/* this isn't what was supposed to happen.. */
		device_printf(sc->sc_dev,
		   "%s: timeout waiting for second alive notice, error %d\n",
		   __func__, error);
		goto fail;
	}
	return 0;
fail:
	return error;
}

void
iwn_unload_firmware(struct iwn_softc *sc)
{
        if (sc->fw_fp != NULL) {
                firmware_put(sc->fw_fp, FIRMWARE_UNLOAD);
                sc->fw_fp = NULL;
        }
}
