int npe_load_firmware()

in ixp4xx/ixp4xx-npe.c [503:663]


int npe_load_firmware(struct npe *npe, const char *name, struct device *dev)
{
	const struct firmware *fw_entry;

	struct dl_block {
		u32 type;
		u32 offset;
	} *blk;

	struct dl_image {
		u32 magic;
		u32 id;
		u32 size;
		union {
			u32 data[0];
			struct dl_block blocks[0];
		};
	} *image;

	struct dl_codeblock {
		u32 npe_addr;
		u32 size;
		u32 data[0];
	} *cb;

	int i, j, err, data_size, instr_size, blocks, table_end;
	u32 cmd;

	if ((err = request_firmware(&fw_entry, name, dev)) != 0)
		return err;

	err = -EINVAL;
	if (fw_entry->size < sizeof(struct dl_image)) {
		print_npe(KERN_ERR, npe, "incomplete firmware file\n");
		goto err;
	}
	image = (struct dl_image*)fw_entry->data;

#if DEBUG_FW
	print_npe(KERN_DEBUG, npe, "firmware: %08X %08X %08X (0x%X bytes)\n",
		  image->magic, image->id, image->size, image->size * 4);
#endif

	if (image->magic == swab32(FW_MAGIC)) { /* swapped file */
		image->id = swab32(image->id);
		image->size = swab32(image->size);
	} else if (image->magic != FW_MAGIC) {
		print_npe(KERN_ERR, npe, "bad firmware file magic: 0x%X\n",
			  image->magic);
		goto err;
	}
	if ((image->size * 4 + sizeof(struct dl_image)) != fw_entry->size) {
		print_npe(KERN_ERR, npe,
			  "inconsistent size of firmware file\n");
		goto err;
	}
	if (((image->id >> 24) & 0xF /* NPE ID */) != npe->id) {
		print_npe(KERN_ERR, npe, "firmware file NPE ID mismatch\n");
		goto err;
	}
	if (image->magic == swab32(FW_MAGIC))
		for (i = 0; i < image->size; i++)
			image->data[i] = swab32(image->data[i]);

	if (cpu_is_ixp42x() && ((image->id >> 28) & 0xF /* device ID */)) {
		print_npe(KERN_INFO, npe, "IXP43x/IXP46x firmware ignored on "
			  "IXP42x\n");
		goto err;
	}

	if (npe_running(npe)) {
		print_npe(KERN_INFO, npe, "unable to load firmware, NPE is "
			  "already running\n");
		err = -EBUSY;
		goto err;
	}
#if 0
	npe_stop(npe);
	npe_reset(npe);
#endif

	print_npe(KERN_INFO, npe, "firmware functionality 0x%X, "
		  "revision 0x%X:%X\n", (image->id >> 16) & 0xFF,
		  (image->id >> 8) & 0xFF, image->id & 0xFF);

	if (cpu_is_ixp42x()) {
		if (!npe->id)
			instr_size = NPE_A_42X_INSTR_SIZE;
		else
			instr_size = NPE_B_AND_C_42X_INSTR_SIZE;
		data_size = NPE_42X_DATA_SIZE;
	} else {
		instr_size = NPE_46X_INSTR_SIZE;
		data_size = NPE_46X_DATA_SIZE;
	}

	for (blocks = 0; blocks * sizeof(struct dl_block) / 4 < image->size;
	     blocks++)
		if (image->blocks[blocks].type == FW_BLOCK_TYPE_EOF)
			break;
	if (blocks * sizeof(struct dl_block) / 4 >= image->size) {
		print_npe(KERN_INFO, npe, "firmware EOF block marker not "
			  "found\n");
		goto err;
	}

#if DEBUG_FW
	print_npe(KERN_DEBUG, npe, "%i firmware blocks found\n", blocks);
#endif

	table_end = blocks * sizeof(struct dl_block) / 4 + 1 /* EOF marker */;
	for (i = 0, blk = image->blocks; i < blocks; i++, blk++) {
		if (blk->offset > image->size - sizeof(struct dl_codeblock) / 4
		    || blk->offset < table_end) {
			print_npe(KERN_INFO, npe, "invalid offset 0x%X of "
				  "firmware block #%i\n", blk->offset, i);
			goto err;
		}

		cb = (struct dl_codeblock*)&image->data[blk->offset];
		if (blk->type == FW_BLOCK_TYPE_INSTR) {
			if (cb->npe_addr + cb->size > instr_size)
				goto too_big;
			cmd = CMD_WR_INS_MEM;
		} else if (blk->type == FW_BLOCK_TYPE_DATA) {
			if (cb->npe_addr + cb->size > data_size)
				goto too_big;
			cmd = CMD_WR_DATA_MEM;
		} else {
			print_npe(KERN_INFO, npe, "invalid firmware block #%i "
				  "type 0x%X\n", i, blk->type);
			goto err;
		}
		if (blk->offset + sizeof(*cb) / 4 + cb->size > image->size) {
			print_npe(KERN_INFO, npe, "firmware block #%i doesn't "
				  "fit in firmware image: type %c, start 0x%X,"
				  " length 0x%X\n", i,
				  blk->type == FW_BLOCK_TYPE_INSTR ? 'I' : 'D',
				  cb->npe_addr, cb->size);
			goto err;
		}

		for (j = 0; j < cb->size; j++)
			npe_cmd_write(npe, cb->npe_addr + j, cmd, cb->data[j]);
	}

	npe_start(npe);
	if (!npe_running(npe))
		print_npe(KERN_ERR, npe, "unable to start\n");
	release_firmware(fw_entry);
	return 0;

too_big:
	print_npe(KERN_INFO, npe, "firmware block #%i doesn't fit in NPE "
		  "memory: type %c, start 0x%X, length 0x%X\n", i,
		  blk->type == FW_BLOCK_TYPE_INSTR ? 'I' : 'D',
		  cb->npe_addr, cb->size);
err:
	release_firmware(fw_entry);
	return err;
}