[PATCH v7] scsi: qla2xxx: Add FC operational firmware load for 29xx

Nilesh Javali <[email protected]> Tue, 11 Aug 2026 15:51:28 +0530
Newsgroups gmane.linux.scsi
Message-ID <[email protected]>
From: Manish Rangankar <[email protected]>

Add support to load the 29xx FC operational firmware from the
filesystem and to set up the corresponding firmware dump
template.  This follows the same request_firmware / segment-load
pattern used by earlier adapters.

Reported-by: kernel test robot <[email protected]>
Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
Signed-off-by: Manish Rangankar <[email protected]>
Signed-off-by: Nilesh Javali <[email protected]>
Reviewed-by: Hannes Reinecke <[email protected]>
---
v7:
In qla29xx_process_rd_image() use the %zx length modifier so the
format is correct on all ABIs.
 drivers/scsi/qla2xxx/qla_fw.h   |  36 +++
 drivers/scsi/qla2xxx/qla_gbl.h  |   3 +-
 drivers/scsi/qla2xxx/qla_init.c | 546 +++++++++++++++++++++++++++++++-
 drivers/scsi/qla2xxx/qla_mbx.c  |  19 +-
 drivers/scsi/qla2xxx/qla_nx.c   |   2 +-
 drivers/scsi/qla2xxx/qla_os.c   |  47 ++-
 drivers/scsi/qla2xxx/qla_sup.c  |   4 +-
 7 files changed, 643 insertions(+), 14 deletions(-)

diff --git a/drivers/scsi/qla2xxx/qla_fw.h b/drivers/scsi/qla2xxx/qla_fw.h
index 42f6125b3cfb..4d6f8b1a36d1 100644
--- a/drivers/scsi/qla2xxx/qla_fw.h
+++ b/drivers/scsi/qla2xxx/qla_fw.h
@@ -2378,4 +2378,40 @@ struct qla_flash_memo_block {
 	struct qla_fmb_version  tool_version;
 };
 
+#define TIM_DEST_ADDR	0xffffffff
+#define CHUNK_SIZE	0x10000
+
+#define TIM	0
+#define ARR1	1
+#define ARR2	2
+#define ARR3	3
+#define ARR4	4
+
+#define LD_FL_HEADER_SIGNATURE	0x46434F50
+#define LD_FL_HEADER_VERSION	0x01
+#define LD_FL_HEADER_SIZE	(0x14 * 4)
+
+struct fcop_header {
+	uint32_t signature;
+	uint32_t header_length;
+	uint32_t header_version;
+	uint32_t segment_size;
+	uint32_t tim_length;
+	uint32_t fc_major_version;
+	uint32_t fc_minor_version;
+	uint32_t fc_subminor_version;
+	uint32_t array1_length;
+	uint32_t array1_destination_addr;
+	uint32_t array2_length;
+	uint32_t array2_destination_addr;
+	uint32_t array3_length;
+	uint32_t array4_length;
+	uint32_t attribute;
+	uint32_t extended_attribute;
+	uint32_t reserved0;
+	uint32_t reserved1;
+	uint32_t reserved2;
+	uint32_t image_checksum;
+};
+
 #endif
diff --git a/drivers/scsi/qla2xxx/qla_gbl.h b/drivers/scsi/qla2xxx/qla_gbl.h
index a032dd047a8f..ca5a80b9c3d2 100644
--- a/drivers/scsi/qla2xxx/qla_gbl.h
+++ b/drivers/scsi/qla2xxx/qla_gbl.h
@@ -38,6 +38,7 @@ extern void qla24xx_update_fw_options(scsi_qla_host_t *);
 extern int qla2x00_load_risc(struct scsi_qla_host *, uint32_t *);
 extern int qla24xx_load_risc(scsi_qla_host_t *, uint32_t *);
 extern int qla81xx_load_risc(scsi_qla_host_t *, uint32_t *);
+extern int qla29xx_load_risc(scsi_qla_host_t *vha, uint32_t *srisc_addr);
 
 extern int qla2x00_perform_loop_resync(scsi_qla_host_t *);
 extern int qla2x00_loop_resync(scsi_qla_host_t *);
@@ -336,7 +337,7 @@ void qla2x00_els_dcmd2_iocb_timeout(void *data);
  * Global Function Prototypes in qla_mbx.c source file.
  */
 extern int
-qla2x00_load_ram(scsi_qla_host_t *, dma_addr_t, uint32_t, uint32_t);
+qla2x00_load_ram(scsi_qla_host_t *, dma_addr_t, uint32_t, uint32_t, int);
 
 extern int
 qla2x00_dump_ram(scsi_qla_host_t *, dma_addr_t, uint32_t, uint32_t);
diff --git a/drivers/scsi/qla2xxx/qla_init.c b/drivers/scsi/qla2xxx/qla_init.c
index c6e928f11758..ebac480ec2e2 100644
--- a/drivers/scsi/qla2xxx/qla_init.c
+++ b/drivers/scsi/qla2xxx/qla_init.c
@@ -3868,7 +3868,7 @@ qla2x00_alloc_fw_dump(scsi_qla_host_t *vha)
 			eft_size = EFT_SIZE;
 	}
 
-	if (IS_QLA27XX(ha) || IS_QLA28XX(ha)) {
+	if (IS_QLA27XX(ha) || IS_QLA28XX(ha) || IS_QLA29XX(ha)) {
 		struct fwdt *fwdt = ha->fwdt;
 		uint j;
 
@@ -3937,7 +3937,8 @@ qla2x00_alloc_fw_dump(scsi_qla_host_t *vha)
 				    "Allocated (%d KB) for firmware dump.\n",
 				    dump_size / 1024);
 
-				if (IS_QLA27XX(ha) || IS_QLA28XX(ha)) {
+				if (IS_QLA27XX(ha) || IS_QLA28XX(ha) ||
+				    IS_QLA29XX(ha)) {
 					ha->mpi_fw_dump = (char *)fw_dump +
 						ha->fwdt[1].dump_size;
 					mutex_unlock(&ha->optrom_mutex);
@@ -8669,7 +8670,8 @@ qla24xx_load_risc_flash(scsi_qla_host_t *vha, uint32_t *srisc_addr,
 			for (i = 0; i < dlen; i++)
 				dcode[i] = swab32(dcode[i]);
 
-			rval = qla2x00_load_ram(vha, req->dma, risc_addr, dlen);
+			rval = qla2x00_load_ram(vha, req->dma, risc_addr,
+			    dlen, 0);
 			if (rval) {
 				ql_log(ql_log_fatal, vha, 0x008f,
 				    "-> Failed load firmware fragment %u.\n",
@@ -8839,7 +8841,7 @@ qla2x00_load_risc(scsi_qla_host_t *vha, uint32_t *srisc_addr)
 				wcode[i] = swab16((__force u32)fwcode[i]);
 
 			rval = qla2x00_load_ram(vha, req->dma, risc_addr,
-			    wlen);
+			    wlen, 0);
 			if (rval) {
 				ql_log(ql_log_fatal, vha, 0x008a,
 				    "Failed to load segment %d of firmware.\n",
@@ -8929,7 +8931,8 @@ qla24xx_load_risc_blob(scsi_qla_host_t *vha, uint32_t *srisc_addr)
 			for (i = 0; i < dlen; i++)
 				dcode[i] = swab32((__force u32)fwcode[i]);
 
-			rval = qla2x00_load_ram(vha, req->dma, risc_addr, dlen);
+			rval = qla2x00_load_ram(vha, req->dma, risc_addr,
+			    dlen, 0);
 			if (rval) {
 				ql_log(ql_log_fatal, vha, 0x0098,
 				    "-> Failed load firmware fragment %u.\n",
@@ -9128,6 +9131,539 @@ qla81xx_load_risc(scsi_qla_host_t *vha, uint32_t *srisc_addr)
 	return rval;
 }
 
+static int qla29xx_validate_firmware_image(scsi_qla_host_t *vha,
+					   __be32 *dword)
+{
+	if (be32_to_cpu(dword[0]) != LD_FL_HEADER_SIGNATURE) {
+		ql_dbg(ql_dbg_init, vha, 0x0093,
+		       "Firmware image Signature does not match (0x%x).\n",
+		       be32_to_cpu(dword[0]));
+		return QLA_FUNCTION_FAILED;
+	}
+	if (be32_to_cpu(dword[2]) != LD_FL_HEADER_VERSION) {
+		ql_dbg(ql_dbg_init, vha, 0x0093,
+		       "Firmware image Version does not match (0x%x).\n",
+		       be32_to_cpu(dword[2]));
+		return QLA_FUNCTION_FAILED;
+	}
+	return QLA_SUCCESS;
+}
+
+static int qla29xx_process_rd_image(struct scsi_qla_host *vha,
+				    struct fcop_header *header,
+				    __be32 *fwcode, int section,
+				    uint32_t risc_addr, uint32_t section_size)
+{
+	int rval = QLA_SUCCESS;
+	uint32_t *dcode = NULL;
+	struct qla_hw_data *ha = vha->hw;
+	struct req_que *req = ha->req_q_map[0];
+	int num_segments = 0, segment = 0;
+	int chunks_per_segment = 0, chunk = 0;
+	int is_first_chunk = 0;
+	int is_last_chunk = 0;
+	int is_first_segment = 0;
+	int is_last_segment = 0;
+	int opt = 0;
+	uint32_t size = 0;
+	uint32_t size_remainder = 0;
+	uint32_t seg_size_remainder = 0;
+	int i = 0;
+
+	dcode = (uint32_t *)req->ring;
+
+	if (section == TIM) {
+		if (section_size > req->length * qla_req_entry_size(ha)) {
+			ql_log(ql_log_fatal, vha, 0x0098,
+			    "TIM section too large (0x%x bytes, ring 0x%zx bytes).\n",
+			    section_size,
+			    req->length * qla_req_entry_size(ha));
+			return QLA_FUNCTION_FAILED;
+		}
+
+		opt = BIT_15 | BIT_2 | BIT_1 | BIT_0;
+		for (i = 0; i < (section_size >> 2); i++)
+			dcode[i] = swab32((__force u32)fwcode[i]);
+
+		ql_dbg(ql_dbg_init, vha, 0x0098,
+		       "TIM : process_rd_image [opt 0x%x]\n", opt);
+		rval = qla2x00_load_ram(vha, req->dma, risc_addr,
+					section_size >> 2, opt);
+		if (rval) {
+			ql_log(ql_log_fatal, vha, 0x0098,
+			       "-> Failed load TIM\n");
+			return QLA_FUNCTION_FAILED;
+		}
+	} else {
+		size_remainder = section_size;
+		num_segments += (section_size % header->segment_size == 0) ?
+			(section_size / header->segment_size) :
+			(section_size / header->segment_size) + 1;
+		chunks_per_segment =
+			(header->segment_size % CHUNK_SIZE == 0) ?
+			(header->segment_size / CHUNK_SIZE) :
+			(header->segment_size / CHUNK_SIZE) + 1;
+
+		ql_dbg(ql_dbg_init, vha, 0x0098,
+		       "num seg 0x%x chunk per seg 0x%x\n",
+		       num_segments, chunks_per_segment);
+
+		for (segment = 0; segment < num_segments; segment++) {
+			for (chunk = 0; chunk < chunks_per_segment; chunk++) {
+				is_first_chunk = (chunk == 0);
+				if (chunk == 0) {
+					if (size_remainder >=
+					    header->segment_size)
+						seg_size_remainder =
+							header->segment_size;
+					else
+						seg_size_remainder =
+							size_remainder;
+				}
+
+				is_first_segment =
+					(section == ARR1 && segment == 0);
+				is_last_segment =
+					(section == ARR2 &&
+					 (segment == (num_segments - 1)));
+				is_last_chunk =
+					(chunk == (chunks_per_segment - 1) ||
+					 size_remainder <= CHUNK_SIZE);
+
+				if (seg_size_remainder < CHUNK_SIZE)
+					size = seg_size_remainder % CHUNK_SIZE;
+				else
+					size = CHUNK_SIZE;
+
+				ql_dbg(ql_dbg_init, vha, 0x0098,
+				       "[%d]chunk 0x%x segment 0x%x first_segment 0x%x first_chunk 0x%x last_segment 0x%x last_chunk 0x%x\n",
+				       __LINE__, chunk, segment,
+				       is_first_segment, is_first_chunk,
+				       is_last_segment, is_last_chunk);
+
+				opt = BIT_2;
+				if (is_first_chunk)
+					opt |= BIT_0;
+				if (is_last_chunk)
+					opt |= BIT_1;
+				if (is_first_segment)
+					opt |= BIT_3;
+				if (is_last_segment)
+					opt |= BIT_4;
+
+				for (i = 0; i < (size >> 2); i++)
+					dcode[i] =
+					    swab32((__force u32)fwcode[i]);
+
+				ql_dbg(ql_dbg_init, vha, 0x0098,
+				       "ARR[0x%x] : opt 0x%x\n", size, opt);
+				rval = qla2x00_load_ram(vha, req->dma,
+							risc_addr,
+							size >> 2, opt);
+				if (rval) {
+					ql_log(ql_log_fatal, vha, 0x0098,
+					       "-> Failed load ram\n");
+					return QLA_FUNCTION_FAILED;
+				}
+
+				fwcode += size >> 2;
+				size_remainder -= size;
+				seg_size_remainder -= size;
+				if (size_remainder == 0)
+					break;
+			}
+			risc_addr += header->segment_size >> 2;
+		}
+	}
+	return rval;
+}
+
+static int
+qla29xx_load_risc_blob(scsi_qla_host_t *vha, uint32_t *srisc_addr)
+{
+	int rval;
+	struct fcop_header *header;
+	uint32_t *dcode = NULL;
+	struct fw_blob *blob;
+	__be32 *fwcode, *array1_addr, *array3_addr;
+	uint templates;
+	uint32_t array_size, risc_attr = 0;
+	ulong i;
+	uint j;
+	ulong dlen, array3_offset;
+	size_t max_dwords;
+	struct qla_hw_data *ha = vha->hw;
+	struct fwdt *fwdt = ha->fwdt;
+
+	header = vmalloc(sizeof(*header));
+	if (!header)
+		return QLA_FUNCTION_FAILED;
+
+	ql_dbg(ql_dbg_init, vha, 0x0090,
+	       "-> FW: Loading via request-firmware.\n");
+
+	blob = qla2x00_request_firmware(vha);
+	if (!blob) {
+		ql_log(ql_log_warn, vha, 0x0092,
+		       "-> Firmware file not found.\n");
+		vfree(header);
+		return QLA_FUNCTION_FAILED;
+	}
+
+	fwcode = (__force __be32 *)blob->fw->data;
+
+	if (blob->fw->size < sizeof(*header)) {
+		ql_log(ql_log_fatal, vha, 0x0093,
+		       "Firmware image too small (%zu bytes, need %zu).\n",
+		       blob->fw->size, sizeof(*header));
+		vfree(header);
+		return QLA_FUNCTION_FAILED;
+	}
+
+	rval = qla29xx_validate_firmware_image(vha, fwcode);
+	if (rval != QLA_SUCCESS) {
+		ql_log(ql_log_fatal, vha, 0x0093,
+		       "Unable to verify integrity of firmware image\n");
+		vfree(header);
+		return QLA_FUNCTION_FAILED;
+	}
+
+	/* byte-swap big-endian firmware header to CPU endian */
+	for (i = 0; i < sizeof(*header) / sizeof(__be32); i++)
+		((uint32_t *)header)[i] = be32_to_cpu(fwcode[i]);
+
+	/*
+	 * Section lengths are dword counts from the firmware image. Reject
+	 * values that would overflow the u32 fields on conversion to bytes
+	 * (<< 2) or that exceed the image, before using them below.
+	 */
+	max_dwords = blob->fw->size >> 2;
+	if (header->header_length > max_dwords ||
+	    header->tim_length > max_dwords ||
+	    header->array1_length > max_dwords ||
+	    header->array2_length > max_dwords ||
+	    header->array3_length > max_dwords ||
+	    header->array4_length > max_dwords) {
+		ql_log(ql_log_fatal, vha, 0x0098,
+		       "FW image has invalid section length.\n");
+		goto img_corrupt;
+	}
+
+	header->header_length <<= 2;
+	header->tim_length <<= 2;
+	header->array1_length <<= 2;
+	header->array2_length <<= 2;
+	header->array3_length <<= 2;
+	header->array4_length <<= 2;
+
+	if (!header->segment_size) {
+		ql_log(ql_log_fatal, vha, 0x0098,
+		       "FW image has zero segment_size.\n");
+		goto img_corrupt;
+	}
+
+	{
+		size_t fw_size = blob->fw->size;
+		size_t offset;
+
+		/* TIM */
+		offset = header->header_length;
+		if (offset + header->tim_length > fw_size) {
+			ql_log(ql_log_fatal, vha, 0x0098,
+			       "FW image too small for TIM (%#zx > %#zx).\n",
+			       offset + header->tim_length, fw_size);
+			goto img_corrupt;
+		}
+		rval = qla29xx_process_rd_image(vha, header,
+			(__be32 *)((char *)fwcode + offset),
+			TIM, TIM_DEST_ADDR, header->tim_length);
+		if (rval) {
+			ql_log(ql_log_fatal, vha, 0x0098,
+			       "-> Failed load TIM\n");
+			goto img_corrupt;
+		}
+
+		/* Array 1 */
+		offset += header->tim_length;
+		if (offset + header->array1_length > fw_size) {
+			ql_log(ql_log_fatal, vha, 0x0098,
+			       "FW image too small for Arr1 (%#zx > %#zx).\n",
+			       offset + header->array1_length, fw_size);
+			goto img_corrupt;
+		}
+		rval = qla29xx_process_rd_image(vha, header,
+			(__be32 *)((char *)fwcode + offset),
+			ARR1, header->array1_destination_addr,
+			header->array1_length);
+		if (rval) {
+			ql_log(ql_log_fatal, vha, 0x0098,
+			       "-> Failed load Arr1\n");
+			goto img_corrupt;
+		}
+
+		/* Array 2 */
+		offset += header->array1_length;
+		if (offset + header->array2_length > fw_size) {
+			ql_log(ql_log_fatal, vha, 0x0098,
+			       "FW image too small for Arr2 (%#zx > %#zx).\n",
+			       offset + header->array2_length, fw_size);
+			goto img_corrupt;
+		}
+		rval = qla29xx_process_rd_image(vha, header,
+			(__be32 *)((char *)fwcode + offset),
+			ARR2, header->array2_destination_addr,
+			header->array2_length);
+		if (rval) {
+			ql_log(ql_log_fatal, vha, 0x0098,
+			       "-> Failed load Arr2\n");
+			goto img_corrupt;
+		}
+
+		/* Array 3: FW_DUMP template */
+		if (offset + 10 * sizeof(__be32) > fw_size) {
+			ql_log(ql_log_fatal, vha, 0x0098,
+			       "FW image too small for risc_attr.\n");
+			goto img_corrupt;
+		}
+		array1_addr = (__be32 *)((char *)fwcode +
+				header->header_length +
+				header->tim_length);
+		risc_attr = be32_to_cpu(array1_addr[9]);
+	}
+	templates = (risc_attr & BIT_9) ? 2 : 1;
+	ql_dbg(ql_dbg_init, vha, 0x0160,
+	       "-> templates = %u\n", templates);
+
+	array3_offset = header->header_length + header->tim_length +
+		header->array1_length + header->array2_length;
+	if (array3_offset > blob->fw->size) {
+		ql_log(ql_log_fatal, vha, 0x0098,
+		       "FW image too small for Arr3 (%#lx > %#zx).\n",
+		       array3_offset, blob->fw->size);
+		goto img_corrupt;
+	}
+
+	array3_addr = (__be32 *)(((char *)fwcode) + array3_offset);
+	for (j = 0; j < templates; j++, fwdt++) {
+		vfree(fwdt->template);
+		fwdt->template = NULL;
+		fwdt->length = 0;
+
+		if ((char *)&array3_addr[7] >
+		    (char *)blob->fw->data + blob->fw->size) {
+			ql_log(ql_log_warn, vha, 0x0169,
+			       "-> fwdt%u template header exceeds firmware blob.\n",
+			       j);
+			goto failed;
+		}
+
+		array_size = be32_to_cpu(array3_addr[2]);
+		ql_dbg(ql_dbg_init, vha, 0x0161,
+		       "-> fwdt%u template array at %p (%#x dwords)\n",
+		       j, array3_addr, array_size);
+
+		if (!array_size || !~array_size || array_size <= 8) {
+			ql_dbg(ql_dbg_init, vha, 0x0162,
+			       "-> fwdt%u failed to read array\n", j);
+			goto failed;
+		}
+
+		array3_addr += 7;
+		array_size -= 8;
+
+		if ((char *)&array3_addr[array_size] >
+		    (char *)blob->fw->data + blob->fw->size) {
+			ql_log(ql_log_warn, vha, 0x0163,
+			    "-> fwdt%u template array exceeds firmware blob.\n",
+			    j);
+			goto failed;
+		}
+
+		ql_dbg(ql_dbg_init, vha, 0x0163,
+		       "-> fwdt%u template allocate template %#x words...\n",
+		       j, array_size);
+		fwdt->template = vmalloc_array(array_size, sizeof(*dcode));
+		if (!fwdt->template) {
+			ql_log(ql_log_warn, vha, 0x0164,
+			       "-> fwdt%u failed allocate template.\n", j);
+			goto failed;
+		}
+
+		dcode = fwdt->template;
+		for (i = 0; i < array_size; i++)
+			dcode[i] = (__force u32)array3_addr[i];
+
+		if (!qla27xx_fwdt_template_valid(dcode)) {
+			ql_log(ql_log_warn, vha, 0x0165,
+			       "-> fwdt%u failed template validate\n", j);
+			goto failed;
+		}
+
+		dlen = qla27xx_fwdt_template_size(dcode);
+		ql_dbg(ql_dbg_init, vha, 0x0166,
+		       "-> fwdt%u template size %#lx bytes (%#lx words)\n",
+		       j, dlen, dlen / sizeof(*dcode));
+		if (dlen > array_size * sizeof(*dcode)) {
+			ql_log(ql_log_warn, vha, 0x0167,
+			       "-> fwdt%u template exceeds array (%-lu bytes)\n",
+			       j, dlen - array_size * sizeof(*dcode));
+			goto failed;
+		}
+
+		fwdt->length = dlen;
+		ql_dbg(ql_dbg_init, vha, 0x0168,
+		       "-> fwdt%u loaded template ok\n", j);
+
+		array3_addr += array_size + 1;
+	}
+	vfree(header);
+	return QLA_SUCCESS;
+
+failed:
+	vfree(fwdt->template);
+	fwdt->template = NULL;
+	fwdt->length = 0;
+img_corrupt:
+	vfree(header);
+	return QLA_FUNCTION_FAILED;
+}
+
+static int
+qla29xx_load_fw_template(scsi_qla_host_t *vha)
+{
+	struct qla_hw_data *ha = vha->hw;
+	struct fwdt *fwdt = ha->fwdt;
+	uint templates = 1;
+	void *fw_dump_tmplt;
+	uint32_t *dcode;
+	void *buf = NULL;
+	uint32_t len;
+	uint32_t template_size;
+	uint j = 0;
+	int rval = 0;
+
+	ql_dbg(ql_dbg_init, vha, 0x01bf,
+	       "-> templates = %u fw_dump_tmplt_len = 0x%x\n",
+	       templates, ha->fw_dump_tmplt_len);
+
+	if (!ha->fw_dump_tmplt_len) {
+		ql_dbg(ql_dbg_init, vha, 0x01bf,
+		       "no fw dump template available\n");
+		return QLA_SUCCESS;
+	}
+
+	fw_dump_tmplt = kzalloc(ha->fw_dump_tmplt_len, GFP_KERNEL);
+	if (!fw_dump_tmplt) {
+		ql_log(ql_log_info, vha, 0x0013,
+		       "Unable to allocate memory\n");
+		return QLA_FUNCTION_FAILED;
+	}
+
+	buf = qla29xx_read_optrom_data(vha, FLT_REG_FW_DUMP_TMPLT, 0,
+				       fw_dump_tmplt, 0,
+				       ha->fw_dump_tmplt_len);
+	if (!buf) {
+		ql_log(ql_log_info, vha, 0x0013,
+		       "Unable to read fw dump temp info.\n");
+		goto free_fw_dump;
+	}
+
+	ql_dump_buffer(ql_dbg_init, vha, 0x006b, fw_dump_tmplt,
+		       min_t(uint32_t, ha->fw_dump_tmplt_len, 1024));
+
+	for (j = 0; j < templates; j++, fwdt++) {
+		vfree(fwdt->template);
+		fwdt->template = NULL;
+		fwdt->length = 0;
+
+		template_size = le32_to_cpu(((__le32 *)fw_dump_tmplt)[2]);
+
+		ql_dbg(ql_dbg_init, vha, 0x0161,
+		       "-> fwdt%u template array at %p (0x%x bytes)\n",
+		       j, fw_dump_tmplt, template_size);
+
+		if (!template_size || !~template_size ||
+		    template_size > ha->fw_dump_tmplt_len) {
+			ql_dbg(ql_dbg_init, vha, 0x0162,
+			       "-> fwdt%u failed to read array\n", j);
+			goto failed;
+		}
+
+		fwdt->template = vmalloc(template_size);
+		if (!fwdt->template) {
+			ql_log(ql_log_warn, vha, 0x0164,
+			       "-> fwdt%u failed allocate template.\n", j);
+			goto failed;
+		}
+
+		dcode = fwdt->template;
+		memcpy((char *)dcode, (char *)fw_dump_tmplt, template_size);
+
+		if (!qla27xx_fwdt_template_valid(dcode)) {
+			ql_log(ql_log_warn, vha, 0x0165,
+			       "-> fwdt%u failed template validate (rval %x)\n",
+			       j, rval);
+			goto failed;
+		}
+
+		len = qla27xx_fwdt_template_size(dcode);
+		if (len > template_size) {
+			ql_log(ql_log_warn, vha, 0x0167,
+			       "-> fwdt%u template exceeds array (%-u bytes)\n",
+			       j, len - template_size);
+			goto failed;
+		}
+
+		fwdt->length = len;
+	}
+
+	kfree(fw_dump_tmplt);
+	return QLA_SUCCESS;
+
+failed:
+	if (fwdt->template) {
+		vfree(fwdt->template);
+		fwdt->template = NULL;
+		fwdt->length = 0;
+	}
+free_fw_dump:
+	kfree(fw_dump_tmplt);
+	return QLA_FUNCTION_FAILED;
+}
+
+int
+qla29xx_load_risc(scsi_qla_host_t *vha, uint32_t *srisc_addr)
+{
+	int rval;
+
+	*srisc_addr = 0x100000;
+
+	if (ql2xfwloadbin == 2) {
+		rval = qla29xx_load_risc_blob(vha, srisc_addr);
+		if (rval != QLA_SUCCESS) {
+			ql_log(ql_log_fatal, vha, 0x019e,
+			       "Failed to load firmware from file.\n");
+			return QLA_FUNCTION_FAILED;
+		}
+	} else {
+		rval = qla28xx_load_flash_firmware(vha);
+		if (rval != QLA_SUCCESS) {
+			ql_log(ql_log_fatal, vha, 0x019e,
+			       "Failed to load flash firmware.\n");
+			return QLA_FUNCTION_FAILED;
+		}
+
+		rval = qla29xx_load_fw_template(vha);
+		if (rval != QLA_SUCCESS) {
+			ql_log(ql_log_warn, vha, 0x01bd,
+			       "failed to read firmware template\n");
+			return QLA_FUNCTION_FAILED;
+		}
+	}
+
+	return rval;
+}
+
 void
 qla2x00_try_to_stop_firmware(scsi_qla_host_t *vha)
 {
diff --git a/drivers/scsi/qla2xxx/qla_mbx.c b/drivers/scsi/qla2xxx/qla_mbx.c
index 2b232b225c90..d49dacf6d576 100644
--- a/drivers/scsi/qla2xxx/qla_mbx.c
+++ b/drivers/scsi/qla2xxx/qla_mbx.c
@@ -618,7 +618,7 @@ qla2x00_mailbox_command(scsi_qla_host_t *vha, mbx_cmd_t *mcp)
 
 int
 qla2x00_load_ram(scsi_qla_host_t *vha, dma_addr_t req_dma, uint32_t risc_addr,
-    uint32_t risc_code_size)
+		 uint32_t risc_code_size, int opt)
 {
 	int rval;
 	struct qla_hw_data *ha = vha->hw;
@@ -651,7 +651,14 @@ qla2x00_load_ram(scsi_qla_host_t *vha, dma_addr_t req_dma, uint32_t risc_addr,
 		mcp->out_mb |= MBX_4;
 	}
 
-	mcp->in_mb = MBX_1|MBX_0;
+	if (IS_QLA29XX(ha)) {
+		mcp->mb[9] = opt;
+		mcp->out_mb |= MBX_9;
+		mcp->in_mb = MBX_3|MBX_2|MBX_1|MBX_0;
+	} else {
+		mcp->in_mb = MBX_1|MBX_0;
+	}
+
 	mcp->tov = MBX_TOV_SECONDS;
 	mcp->flags = 0;
 	rval = qla2x00_mailbox_command(vha, mcp);
@@ -846,15 +853,19 @@ qla28xx_load_flash_firmware(scsi_qla_host_t *vha)
 	mbx_cmd_t mc;
 	mbx_cmd_t *mcp = &mc;
 
-	if (!IS_QLA28XX(ha))
+	if (!IS_QLA28XX(ha) && !IS_QLA29XX(ha))
 		return rval;
 
 	ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x11a6,
 	       "Entered %s.\n", __func__);
 
 	mcp->mb[0] = MBC_LOAD_FLASH_FIRMWARE;
+	mcp->mb[1] = 0;
 	mcp->out_mb = MBX_2 | MBX_1 | MBX_0;
-	mcp->in_mb = MBX_0;
+	if (IS_QLA29XX(ha))
+		mcp->in_mb = MBX_1 | MBX_0;
+	else
+		mcp->in_mb = MBX_0;
 	mcp->tov = MBX_TOV_SECONDS;
 	mcp->flags = 0;
 	rval = qla2x00_mailbox_command(vha, mcp);
diff --git a/drivers/scsi/qla2xxx/qla_nx.c b/drivers/scsi/qla2xxx/qla_nx.c
index 298c060c1292..2366cca51bce 100644
--- a/drivers/scsi/qla2xxx/qla_nx.c
+++ b/drivers/scsi/qla2xxx/qla_nx.c
@@ -2663,7 +2663,7 @@ qla82xx_write_flash_data(struct scsi_qla_host *vha, __le32 *dwptr,
 
 			ret = qla2x00_load_ram(vha, optrom_dma,
 			    (ha->flash_data_off | faddr),
-			    OPTROM_BURST_DWORDS);
+			    OPTROM_BURST_DWORDS, 0);
 			if (ret != QLA_SUCCESS) {
 				ql_log(ql_log_warn, vha, 0xb01e,
 				    "Unable to burst-write optrom segment "
diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c
index c3eb941f71ad..6560e58ad87c 100644
--- a/drivers/scsi/qla2xxx/qla_os.c
+++ b/drivers/scsi/qla2xxx/qla_os.c
@@ -2640,6 +2640,45 @@ static struct isp_operations qla27xx_isp_ops = {
 	.initialize_adapter	= qla2x00_initialize_adapter,
 };
 
+static struct isp_operations qla29xx_isp_ops = {
+	.pci_config		= qla25xx_pci_config,
+	.reset_chip		= qla24xx_reset_chip,
+	.chip_diag		= qla24xx_chip_diag,
+	.config_rings		= qla24xx_config_rings,
+	.reset_adapter		= qla24xx_reset_adapter,
+	.nvram_config		= qla81xx_nvram_config,
+	.update_fw_options	= qla24xx_update_fw_options,
+	.load_risc		= qla29xx_load_risc,
+	.pci_info_str		= qla24xx_pci_info_str,
+	.fw_version_str		= qla24xx_fw_version_str,
+	.intr_handler		= qla24xx_intr_handler,
+	.enable_intrs		= qla24xx_enable_intrs,
+	.disable_intrs		= qla24xx_disable_intrs,
+	.abort_command		= qla24xx_abort_command,
+	.target_reset		= qla24xx_abort_target,
+	.lun_reset		= qla24xx_lun_reset,
+	.fabric_login		= qla24xx_login_fabric,
+	.fabric_logout		= qla24xx_fabric_logout,
+	.calc_req_entries	= NULL,
+	.build_iocbs		= NULL,
+	.prep_ms_iocb		= qla24xx_prep_ms_iocb,
+	.prep_ms_fdmi_iocb	= qla24xx_prep_ms_fdmi_iocb,
+	.read_nvram		= NULL,
+	.write_nvram		= NULL,
+	.fw_dump		= qla27xx_fwdump,
+	.mpi_fw_dump		= qla27xx_mpi_fwdump,
+	.beacon_on		= qla24xx_beacon_on,
+	.beacon_off		= qla24xx_beacon_off,
+	.beacon_blink		= qla83xx_beacon_blink,
+	.read_optrom		= qla25xx_read_optrom_data,
+	.write_optrom		= qla24xx_write_optrom_data,
+	.get_flash_version	= qla24xx_get_flash_version,
+	.start_scsi_mq		= qla2xxx_dif_start_scsi_mq,
+	.abort_isp		= qla2x00_abort_isp,
+	.iospace_config		= qla83xx_iospace_config,
+	.initialize_adapter	= qla2x00_initialize_adapter,
+};
+
 static inline void
 qla2x00_set_isp_flags(struct qla_hw_data *ha)
 {
@@ -3232,7 +3271,7 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id)
 		ha->gid_list_info_size = 8;
 		ha->optrom_size = OPTROM_SIZE_28XX;
 		ha->nvram_npiv_size = QLA_MAX_VPORTS_QLA25XX;
-		ha->isp_ops = &qla27xx_isp_ops;
+		ha->isp_ops = &qla29xx_isp_ops;
 		ha->flash_conf_off = ~0;
 		ha->flash_data_off = ~0;
 		ha->nvram_conf_off = ~0;
@@ -7676,6 +7715,7 @@ qla2x00_timer(struct timer_list *t)
 #define FW_ISP8031	9
 #define FW_ISP27XX	10
 #define FW_ISP28XX	11
+#define FW_ISP29XX	12
 
 #define FW_FILE_ISP21XX	"ql2100_fw.bin"
 #define FW_FILE_ISP22XX	"ql2200_fw.bin"
@@ -7689,6 +7729,7 @@ qla2x00_timer(struct timer_list *t)
 #define FW_FILE_ISP8031	"ql8300_fw.bin"
 #define FW_FILE_ISP27XX	"ql2700_fw.bin"
 #define FW_FILE_ISP28XX	"ql2800_fw.bin"
+#define FW_FILE_ISP29XX	"ql2900_fw.bin"
 
 
 static DEFINE_MUTEX(qla_fw_lock);
@@ -7706,6 +7747,7 @@ static struct fw_blob qla_fw_blobs[] = {
 	{ .name = FW_FILE_ISP8031, },
 	{ .name = FW_FILE_ISP27XX, },
 	{ .name = FW_FILE_ISP28XX, },
+	{ .name = FW_FILE_ISP29XX, },
 	{ .name = NULL, },
 };
 
@@ -7739,6 +7781,8 @@ qla2x00_request_firmware(scsi_qla_host_t *vha)
 		blob = &qla_fw_blobs[FW_ISP27XX];
 	} else if (IS_QLA28XX(ha)) {
 		blob = &qla_fw_blobs[FW_ISP28XX];
+	} else if (IS_QLA29XX(ha)) {
+		blob = &qla_fw_blobs[FW_ISP29XX];
 	} else {
 		return NULL;
 	}
@@ -8445,3 +8489,4 @@ MODULE_FIRMWARE(FW_FILE_ISP2300);
 MODULE_FIRMWARE(FW_FILE_ISP2322);
 MODULE_FIRMWARE(FW_FILE_ISP24XX);
 MODULE_FIRMWARE(FW_FILE_ISP25XX);
+MODULE_FIRMWARE(FW_FILE_ISP29XX);
diff --git a/drivers/scsi/qla2xxx/qla_sup.c b/drivers/scsi/qla2xxx/qla_sup.c
index ea5e4901cc70..ee7562bf8d38 100644
--- a/drivers/scsi/qla2xxx/qla_sup.c
+++ b/drivers/scsi/qla2xxx/qla_sup.c
@@ -1913,7 +1913,7 @@ qla24xx_write_flash_data(scsi_qla_host_t *vha, __le32 *dwptr, uint32_t faddr,
 			ql_log(ql_log_warn + ql_dbg_verbose, vha, 0x7095,
 			    "Write burst (%#lx dwords)...\n", dburst);
 			ret = qla2x00_load_ram(vha, optrom_dma,
-			    flash_data_addr(ha, faddr), dburst);
+			    flash_data_addr(ha, faddr), dburst, 0);
 			if (!ret) {
 				liter += dburst - 1;
 				faddr += dburst - 1;
@@ -3513,7 +3513,7 @@ qla28xx_write_flash_data(scsi_qla_host_t *vha, uint32_t *dwptr, uint32_t faddr,
 		ql_log(ql_log_warn + ql_dbg_verbose, vha, 0x7095,
 		    "Write burst (%#lx dwords)...\n", dburst);
 		rval = qla2x00_load_ram(vha, optrom_dma,
-		    flash_data_addr(ha, faddr), dburst);
+		    flash_data_addr(ha, faddr), dburst, 0);
 		if (rval != QLA_SUCCESS) {
 			ql_log(ql_log_warn, vha, 0x7097,
 			    "Failed burst write at %x (%p/%#llx)...\n",
-- 
2.52.0