Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a8c9a53c authored by David Dillow's avatar David Dillow Committed by David S. Miller
Browse files

typhoon: repair firmware loading



The conversion to avoid using pci_alloc_consistent() broke the firmware
load process, as well as added an order-4 kmalloc and doubled the memory
usage of the firmware image. Go back to loading a page at a time.

Also, since the user can now give us utter garbage for firmware, do a
cursory validation so we don't try to load just anything.

Signed-off-by: default avatarDavid Dillow <dave@thedillows.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent d20b606c
Loading
Loading
Loading
Loading
+83 −54
Original line number Diff line number Diff line
@@ -100,10 +100,11 @@ static const int multicast_filter_limit = 32;
#define PKT_BUF_SZ		1536

#define DRV_MODULE_NAME		"typhoon"
#define DRV_MODULE_VERSION 	"1.5.8"
#define DRV_MODULE_RELDATE	"06/11/09"
#define DRV_MODULE_VERSION 	"1.5.9"
#define DRV_MODULE_RELDATE	"Mar 2, 2009"
#define PFX			DRV_MODULE_NAME ": "
#define ERR_PFX			KERN_ERR PFX
#define FIRMWARE_NAME		"3com/typhoon.bin"

#include <linux/module.h>
#include <linux/kernel.h>
@@ -136,7 +137,6 @@ static const int multicast_filter_limit = 32;
static char version[] __devinitdata =
    "typhoon.c: version " DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n";

#define FIRMWARE_NAME		"3com/typhoon.bin"
MODULE_AUTHOR("David Dillow <dave@thedillows.org>");
MODULE_VERSION(DRV_MODULE_VERSION);
MODULE_LICENSE("GPL");
@@ -1347,11 +1347,16 @@ typhoon_init_rings(struct typhoon *tp)
}

static const struct firmware *typhoon_fw;
static u8 *typhoon_fw_image;

static int
typhoon_request_firmware(struct typhoon *tp)
{
	const struct typhoon_file_header *fHdr;
	const struct typhoon_section_header *sHdr;
	const u8 *image_data;
	u32 numSections;
	u32 section_len;
	u32 remaining;
	int err;

	if (typhoon_fw)
@@ -1364,27 +1369,41 @@ typhoon_request_firmware(struct typhoon *tp)
		return err;
	}

	if (typhoon_fw->size < sizeof(struct typhoon_file_header) ||
	    memcmp(typhoon_fw->data, "TYPHOON", 8)) {
		printk(KERN_ERR "%s: Invalid firmware image\n",
		       tp->name);
		err = -EINVAL;
		goto out_err;
	}
	image_data = (u8 *) typhoon_fw->data;
	remaining = typhoon_fw->size;
	if (remaining < sizeof(struct typhoon_file_header))
		goto invalid_fw;

	typhoon_fw_image = kmalloc(typhoon_fw->size, GFP_KERNEL);
	if (!typhoon_fw_image) {
		err = -ENOMEM;
		goto out_err;
	fHdr = (struct typhoon_file_header *) image_data;
	if (memcmp(fHdr->tag, "TYPHOON", 8))
		goto invalid_fw;

	numSections = le32_to_cpu(fHdr->numSections);
	image_data += sizeof(struct typhoon_file_header);
	remaining -= sizeof(struct typhoon_file_header);

	while (numSections--) {
		if (remaining < sizeof(struct typhoon_section_header))
			goto invalid_fw;

		sHdr = (struct typhoon_section_header *) image_data;
		image_data += sizeof(struct typhoon_section_header);
		section_len = le32_to_cpu(sHdr->len);

		if (remaining < section_len)
			goto invalid_fw;

		image_data += section_len;
		remaining -= section_len;
	}
	memcpy(typhoon_fw_image, typhoon_fw->data, typhoon_fw->size);

	return 0;

out_err:
invalid_fw:
	printk(KERN_ERR "%s: Invalid firmware image\n", tp->name);
	release_firmware(typhoon_fw);
	typhoon_fw = NULL;
	return err;
	return -EINVAL;
}

static int
@@ -1395,24 +1414,29 @@ typhoon_download_firmware(struct typhoon *tp)
	const struct typhoon_file_header *fHdr;
	const struct typhoon_section_header *sHdr;
	const u8 *image_data;
	dma_addr_t image_dma;
	void *dpage;
	dma_addr_t dpage_dma;
	__sum16 csum;
	u32 irqEnabled;
	u32 irqMasked;
	u32 numSections;
	u32 section_len;
	u32 len;
	u32 load_addr;
	u32 hmac;
	int i;
	int err;

	image_data = typhoon_fw_image;
	image_data = (u8 *) typhoon_fw->data;
	fHdr = (struct typhoon_file_header *) image_data;

	/* Cannot just map the firmware image using pci_map_single() as
	 * the firmware is vmalloc()'d and may not be physically contiguous,
	 * so we allocate some consistent memory to copy the sections into.
	 */
	err = -ENOMEM;
	image_dma = pci_map_single(pdev, (u8 *) image_data,
				   typhoon_fw->size, PCI_DMA_TODEVICE);
	if (pci_dma_mapping_error(pdev, image_dma)) {
	dpage = pci_alloc_consistent(pdev, PAGE_SIZE, &dpage_dma);
	if(!dpage) {
		printk(KERN_ERR "%s: no DMA mem for firmware\n", tp->name);
		goto err_out;
	}
@@ -1460,6 +1484,9 @@ typhoon_download_firmware(struct typhoon *tp)
		load_addr = le32_to_cpu(sHdr->startAddr);
		section_len = le32_to_cpu(sHdr->len);

		while(section_len) {
			len = min_t(u32, section_len, PAGE_SIZE);

			if(typhoon_wait_interrupt(ioaddr) < 0 ||
			   ioread32(ioaddr + TYPHOON_REG_STATUS) !=
			   TYPHOON_STATUS_WAITING_FOR_SEGMENT) {
@@ -1473,21 +1500,25 @@ typhoon_download_firmware(struct typhoon *tp)
			 * summing. Fortunately, due to the properties of
			 * the checksum, we can do this once, at the end.
			 */
		csum = csum_fold(csum_partial(image_data, section_len, 0));
			csum = csum_fold(csum_partial_copy_nocheck(image_data,
								  dpage, len,
								  0));

		iowrite32(section_len, ioaddr + TYPHOON_REG_BOOT_LENGTH);
			iowrite32(len, ioaddr + TYPHOON_REG_BOOT_LENGTH);
			iowrite32(le16_to_cpu((__force __le16)csum),
					ioaddr + TYPHOON_REG_BOOT_CHECKSUM);
			iowrite32(load_addr,
					ioaddr + TYPHOON_REG_BOOT_DEST_ADDR);
			iowrite32(0, ioaddr + TYPHOON_REG_BOOT_DATA_HI);
		iowrite32(image_dma + (image_data - typhoon_fw_image),
			  ioaddr + TYPHOON_REG_BOOT_DATA_LO);
			iowrite32(dpage_dma, ioaddr + TYPHOON_REG_BOOT_DATA_LO);
			typhoon_post_pci_writes(ioaddr);
			iowrite32(TYPHOON_BOOTCMD_SEG_AVAILABLE,
			       ioaddr + TYPHOON_REG_COMMAND);

		image_data += section_len;
			image_data += len;
			load_addr += len;
			section_len -= len;
		}
	}

	if(typhoon_wait_interrupt(ioaddr) < 0 ||
@@ -1511,7 +1542,7 @@ typhoon_download_firmware(struct typhoon *tp)
	iowrite32(irqMasked, ioaddr + TYPHOON_REG_INTR_MASK);
	iowrite32(irqEnabled, ioaddr + TYPHOON_REG_INTR_ENABLE);

	pci_unmap_single(pdev, image_dma,  typhoon_fw->size, PCI_DMA_TODEVICE);
	pci_free_consistent(pdev, PAGE_SIZE, dpage, dpage_dma);

err_out:
	return err;
@@ -2651,10 +2682,8 @@ typhoon_init(void)
static void __exit
typhoon_cleanup(void)
{
	if (typhoon_fw) {
		kfree(typhoon_fw_image);
	if (typhoon_fw)
		release_firmware(typhoon_fw);
	}
	pci_unregister_driver(&typhoon_driver);
}