/*--------------------------------------------------------------------------- FT1000 driver for Flarion Flash OFDM NIC Device Copyright (C) 2002 Flarion Technologies, All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -------------------------------------------------------------------------- Description: This module will handshake with the DSP bootloader to download the DSP runtime image. ---------------------------------------------------------------------------*/ #define __KERNEL_SYSCALLS__ #include #include #include #include #include #include #include #include #include #include #include #include "ft1000.h" #include "boot.h" #ifdef FT_DEBUG #define DEBUG(n, args...) printk(KERN_DEBUG args); #else #define DEBUG(n, args...) #endif #define MAX_DSP_WAIT_LOOPS 100 #define DSP_WAIT_SLEEP_TIME 1 /* 1 millisecond */ #define MAX_LENGTH 0x7f0 #define DWNLD_MAG_HANDSHAKE_LOC 0x00 #define DWNLD_MAG_TYPE_LOC 0x01 #define DWNLD_MAG_SIZE_LOC 0x02 #define DWNLD_MAG_PS_HDR_LOC 0x03 #define DWNLD_HANDSHAKE_LOC 0x02 #define DWNLD_TYPE_LOC 0x04 #define DWNLD_SIZE_MSW_LOC 0x06 #define DWNLD_SIZE_LSW_LOC 0x08 #define DWNLD_PS_HDR_LOC 0x0A #define HANDSHAKE_TIMEOUT_VALUE 0xF1F1 #define HANDSHAKE_RESET_VALUE 0xFEFE /* When DSP requests startover */ #define HANDSHAKE_DSP_BL_READY 0xFEFE /* At start DSP writes this when bootloader ready */ #define HANDSHAKE_DRIVER_READY 0xFFFF /* Driver writes after receiving 0xFEFE */ #define HANDSHAKE_SEND_DATA 0x0000 /* DSP writes this when ready for more data */ #define HANDSHAKE_REQUEST 0x0001 /* Request from DSP */ #define HANDSHAKE_RESPONSE 0x0000 /* Satisfied DSP request */ #define REQUEST_CODE_LENGTH 0x0000 #define REQUEST_RUN_ADDRESS 0x0001 #define REQUEST_CODE_SEGMENT 0x0002 /* In WORD count */ #define REQUEST_DONE_BL 0x0003 #define REQUEST_DONE_CL 0x0004 #define REQUEST_VERSION_INFO 0x0005 #define REQUEST_CODE_BY_VERSION 0x0006 #define REQUEST_MAILBOX_DATA 0x0007 #define REQUEST_FILE_CHECKSUM 0x0008 #define STATE_START_DWNLD 0x01 #define STATE_BOOT_DWNLD 0x02 #define STATE_CODE_DWNLD 0x03 #define STATE_DONE_DWNLD 0x04 #define STATE_SECTION_PROV 0x05 #define STATE_DONE_PROV 0x06 #define STATE_DONE_FILE 0x07 u16 get_handshake(struct net_device *dev, u16 expected_value); void put_handshake(struct net_device *dev, u16 handshake_value); u16 get_request_type(struct net_device *dev); long get_request_value(struct net_device *dev); void put_request_value(struct net_device *dev, long lvalue); u16 hdr_checksum(struct pseudo_hdr *pHdr); struct dsp_file_hdr { u32 version_id; // Version ID of this image format. u32 package_id; // Package ID of code release. u32 build_date; // Date/time stamp when file was built. u32 commands_offset; // Offset to attached commands in Pseudo Hdr format. u32 loader_offset; // Offset to bootloader code. u32 loader_code_address; // Start address of bootloader. u32 loader_code_end; // Where bootloader code ends. u32 loader_code_size; u32 version_data_offset; // Offset were scrambled version data begins. u32 version_data_size; // Size, in words, of scrambled version data. u32 nDspImages; // Number of DSP images in file. } __attribute__ ((packed)); struct dsp_image_info { u32 coff_date; // Date/time when DSP Coff image was built. u32 begin_offset; // Offset in file where image begins. u32 end_offset; // Offset in file where image begins. u32 run_address; // On chip Start address of DSP code. u32 image_size; // Size of image. u32 version; // Embedded version # of DSP code. unsigned short checksum; // Dsp File checksum unsigned short pad1; } __attribute__ ((packed)); void card_bootload(struct net_device *dev) { struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev); unsigned long flags; u32 *pdata; u32 size; u32 i; u32 templong; DEBUG(0, "card_bootload is called\n"); pdata = (u32 *) bootimage; size = sizeof(bootimage); // check for odd word if (size & 0x0003) { size += 4; } // Provide mutual exclusive access while reading ASIC registers. spin_lock_irqsave(&info->dpram_lock, flags); // need to set i/o base address initially and hardware will autoincrement ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, FT1000_DPRAM_BASE); // write bytes for (i = 0; i < (size >> 2); i++) { templong = *pdata++; outl(templong, dev->base_addr + FT1000_REG_MAG_DPDATA); } spin_unlock_irqrestore(&info->dpram_lock, flags); } u16 get_handshake(struct net_device *dev, u16 expected_value) { struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev); u16 handshake; u32 tempx; int loopcnt; loopcnt = 0; while (loopcnt < MAX_DSP_WAIT_LOOPS) { if (info->AsicID == ELECTRABUZZ_ID) { ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, DWNLD_HANDSHAKE_LOC); handshake = ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA); } else { tempx = ntohl(ft1000_read_dpram_mag_32 (dev, DWNLD_MAG_HANDSHAKE_LOC)); handshake = (u16) tempx; } if ((handshake == expected_value) || (handshake == HANDSHAKE_RESET_VALUE)) { return handshake; } else { loopcnt++; mdelay(DSP_WAIT_SLEEP_TIME); } } return HANDSHAKE_TIMEOUT_VALUE; } void put_handshake(struct net_device *dev, u16 handshake_value) { struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev); u32 tempx; if (info->AsicID == ELECTRABUZZ_ID) { ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, DWNLD_HANDSHAKE_LOC); ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, handshake_value); /* Handshake */ } else { tempx = (u32) handshake_value; tempx = ntohl(tempx); ft1000_write_dpram_mag_32(dev, DWNLD_MAG_HANDSHAKE_LOC, tempx); /* Handshake */ } } u16 get_request_type(struct net_device *dev) { struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev); u16 request_type; u32 tempx; if (info->AsicID == ELECTRABUZZ_ID) { ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, DWNLD_TYPE_LOC); request_type = ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA); } else { tempx = ft1000_read_dpram_mag_32(dev, DWNLD_MAG_TYPE_LOC); tempx = ntohl(tempx); request_type = (u16) tempx; } return request_type; } long get_request_value(struct net_device *dev) { struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev); long value; u16 w_val; if (info->AsicID == ELECTRABUZZ_ID) { ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, DWNLD_SIZE_MSW_LOC); w_val = ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA); value = (long)(w_val << 16); ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, DWNLD_SIZE_LSW_LOC); w_val = ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA); value = (long)(value | w_val); } else { value = ft1000_read_dpram_mag_32(dev, DWNLD_MAG_SIZE_LOC); value = ntohl(value); } return value; } void put_request_value(struct net_device *dev, long lvalue) { struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev); u16 size; u32 tempx; if (info->AsicID == ELECTRABUZZ_ID) { size = (u16) (lvalue >> 16); ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, DWNLD_SIZE_MSW_LOC); ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, size); size = (u16) (lvalue); ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, DWNLD_SIZE_LSW_LOC); ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, size); } else { tempx = ntohl(lvalue); ft1000_write_dpram_mag_32(dev, DWNLD_MAG_SIZE_LOC, tempx); /* Handshake */ } } u16 hdr_checksum(struct pseudo_hdr *pHdr) { u16 *usPtr = (u16 *) pHdr; u16 chksum; chksum = ((((((usPtr[0] ^ usPtr[1]) ^ usPtr[2]) ^ usPtr[3]) ^ usPtr[4]) ^ usPtr[5]) ^ usPtr[6]); return chksum; } int card_download(struct net_device *dev, const u8 *pFileStart, size_t FileLength) { struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev); int Status = SUCCESS; u32 uiState; u16 handshake; struct pseudo_hdr *pHdr; u16 usHdrLength; long word_length; u16 request; u16 temp; struct prov_record *pprov_record; u8 *pbuffer; struct dsp_file_hdr *pFileHdr5; struct dsp_image_info *pDspImageInfoV6 = NULL; long requested_version; bool bGoodVersion = 0; struct drv_msg *pMailBoxData; u16 *pUsData = NULL; u16 *pUsFile = NULL; u8 *pUcFile = NULL; u8 *pBootEnd = NULL; u8 *pCodeEnd = NULL; int imageN; long file_version; long loader_code_address = 0; long loader_code_size = 0; long run_address = 0; long run_size = 0; unsigned long flags; unsigned long templong; unsigned long image_chksum = 0; file_version = *(long *)pFileStart; if (file_version != 6) { printk(KERN_ERR "ft1000: unsupported firmware version %ld\n", file_version); Status = FAILURE; } uiState = STATE_START_DWNLD; pFileHdr5 = (struct dsp_file_hdr *) pFileStart; pUsFile = (u16 *) ((long)pFileStart + pFileHdr5->loader_offset); pUcFile = (u8 *) ((long)pFileStart + pFileHdr5->loader_offset); pBootEnd = (u8 *) ((long)pFileStart + pFileHdr5->loader_code_end); loader_code_address = pFileHdr5->loader_code_address; loader_code_size = pFileHdr5->loader_code_size; bGoodVersion = false; while ((Status == SUCCESS) && (uiState != STATE_DONE_FILE)) { switch (uiState) { case STATE_START_DWNLD: handshake = get_handshake(dev, HANDSHAKE_DSP_BL_READY); if (handshake == HANDSHAKE_DSP_BL_READY) { put_handshake(dev, HANDSHAKE_DRIVER_READY); } else { Status = FAILURE; } uiState = STATE_BOOT_DWNLD; break; case STATE_BOOT_DWNLD: handshake = get_handshake(dev, HANDSHAKE_REQUEST); if (handshake == HANDSHAKE_REQUEST) { /* * Get type associated with the request. */ request = get_request_type(dev); switch (request) { case REQUEST_RUN_ADDRESS: put_request_value(dev, loader_code_address); break; case REQUEST_CODE_LENGTH: put_request_value(dev, loader_code_size); break; case REQUEST_DONE_BL: /* Reposition ptrs to beginning of code section */ pUsFile = (u16 *) ((long)pBootEnd); pUcFile = (u8 *) ((long)pBootEnd); uiState = STATE_CODE_DWNLD; break; case REQUEST_CODE_SEGMENT: word_length = get_request_value(dev); if (word_length > MAX_LENGTH) { Status = FAILURE; break; } if ((word_length * 2 + (long)pUcFile) > (long)pBootEnd) { /* * Error, beyond boot code range. */ Status = FAILURE; break; } // Provide mutual exclusive access while reading ASIC registers. spin_lock_irqsave(&info->dpram_lock, flags); /* * Position ASIC DPRAM auto-increment pointer. */ outw(DWNLD_MAG_PS_HDR_LOC, dev->base_addr + FT1000_REG_DPRAM_ADDR); if (word_length & 0x01) word_length++; word_length = word_length / 2; for (; word_length > 0; word_length--) { /* In words */ templong = *pUsFile++; templong |= (*pUsFile++ << 16); pUcFile += 4; outl(templong, dev->base_addr + FT1000_REG_MAG_DPDATAL); } spin_unlock_irqrestore(&info-> dpram_lock, flags); break; default: Status = FAILURE; break; } put_handshake(dev, HANDSHAKE_RESPONSE); } else { Status = FAILURE; } break; case STATE_CODE_DWNLD: handshake = get_handshake(dev, HANDSHAKE_REQUEST); if (handshake == HANDSHAKE_REQUEST) { /* * Get type associated with the request. */ request = get_request_type(dev); switch (request) { case REQUEST_FILE_CHECKSUM: DEBUG(0, "ft1000_dnld: REQUEST_FOR_CHECKSUM\n"); put_request_value(dev, image_chksum); break; case REQUEST_RUN_ADDRESS: if (bGoodVersion) { put_request_value(dev, run_address); } else { Status = FAILURE; break; } break; case REQUEST_CODE_LENGTH: if (bGoodVersion) { put_request_value(dev, run_size); } else { Status = FAILURE; break; } break; case REQUEST_DONE_CL: /* Reposition ptrs to beginning of provisioning section */ pUsFile = (u16 *) ((long)pFileStart + pFileHdr5->commands_offset); pUcFile = (u8 *) ((long)pFileStart + pFileHdr5->commands_offset); uiState = STATE_DONE_DWNLD; break; case REQUEST_CODE_SEGMENT: if (!bGoodVersion) { Status = FAILURE; break; } word_length = get_request_value(dev); if (word_length > MAX_LENGTH) { Status = FAILURE; break; } if ((word_length * 2 + (long)pUcFile) > (long)pCodeEnd) { /* * Error, beyond boot code range. */ Status = FAILURE; break; } /* * Position ASIC DPRAM auto-increment pointer. */ outw(DWNLD_MAG_PS_HDR_LOC, dev->base_addr + FT1000_REG_DPRAM_ADDR); if (word_length & 0x01) word_length++; word_length = word_length / 2; for (; word_length > 0; word_length--) { /* In words */ templong = *pUsFile++; templong |= (*pUsFile++ << 16); pUcFile += 4; outl(templong, dev->base_addr + FT1000_REG_MAG_DPDATAL); } break; case REQUEST_MAILBOX_DATA: // Convert length from byte count to word count. Make sure we round up. word_length = (long)(info->DSPInfoBlklen + 1) / 2; put_request_value(dev, word_length); pMailBoxData = (struct drv_msg *) & info->DSPInfoBlk[0]; pUsData = (u16 *) & pMailBoxData->data[0]; // Provide mutual exclusive access while reading ASIC registers. spin_lock_irqsave(&info->dpram_lock, flags); if (file_version == 5) { /* * Position ASIC DPRAM auto-increment pointer. */ ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, DWNLD_PS_HDR_LOC); for (; word_length > 0; word_length--) { /* In words */ temp = ntohs(*pUsData); ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, temp); pUsData++; } } else { /* * Position ASIC DPRAM auto-increment pointer. */ outw(DWNLD_MAG_PS_HDR_LOC, dev->base_addr + FT1000_REG_DPRAM_ADDR); if (word_length & 0x01) { word_length++; } word_length = word_length / 2; for (; word_length > 0; word_length--) { /* In words */ templong = *pUsData++; templong |= (*pUsData++ << 16); outl(templong, dev->base_addr + FT1000_REG_MAG_DPDATAL); } } spin_unlock_irqrestore(&info-> dpram_lock, flags); break; case REQUEST_VERSION_INFO: word_length = pFileHdr5->version_data_size; put_request_value(dev, word_length); pUsFile = (u16 *) ((long)pFileStart + pFileHdr5-> version_data_offset); // Provide mutual exclusive access while reading ASIC registers. spin_lock_irqsave(&info->dpram_lock, flags); /* * Position ASIC DPRAM auto-increment pointer. */ outw(DWNLD_MAG_PS_HDR_LOC, dev->base_addr + FT1000_REG_DPRAM_ADDR); if (word_length & 0x01) word_length++; word_length = word_length / 2; for (; word_length > 0; word_length--) { /* In words */ templong = ntohs(*pUsFile++); temp = ntohs(*pUsFile++); templong |= (temp << 16); outl(templong, dev->base_addr + FT1000_REG_MAG_DPDATAL); } spin_unlock_irqrestore(&info-> dpram_lock, flags); break; case REQUEST_CODE_BY_VERSION: bGoodVersion = false; requested_version = get_request_value(dev); pDspImageInfoV6 = (struct dsp_image_info *) ((long) pFileStart + sizeof (struct dsp_file_hdr)); for (imageN = 0; imageN < pFileHdr5->nDspImages; imageN++) { temp = (u16) (pDspImageInfoV6-> version); templong = temp; temp = (u16) (pDspImageInfoV6-> version >> 16); templong |= (temp << 16); if (templong == requested_version) { bGoodVersion = true; pUsFile = (u16 *) ((long) pFileStart + pDspImageInfoV6-> begin_offset); pUcFile = (u8 *) ((long) pFileStart + pDspImageInfoV6-> begin_offset); pCodeEnd = (u8 *) ((long) pFileStart + pDspImageInfoV6-> end_offset); run_address = pDspImageInfoV6-> run_address; run_size = pDspImageInfoV6-> image_size; image_chksum = (u32) pDspImageInfoV6-> checksum; DEBUG(0, "ft1000_dnld: image_chksum = 0x%8x\n", (unsigned int) image_chksum); break; } pDspImageInfoV6++; } if (!bGoodVersion) { /* * Error, beyond boot code range. */ Status = FAILURE; break; } break; default: Status = FAILURE; break; } put_handshake(dev, HANDSHAKE_RESPONSE); } else { Status = FAILURE; } break; case STATE_DONE_DWNLD: if (((unsigned long) (pUcFile) - (unsigned long) pFileStart) >= (unsigned long) FileLength) { uiState = STATE_DONE_FILE; break; } pHdr = (struct pseudo_hdr *) pUsFile; if (pHdr->portdest == 0x80 /* DspOAM */ && (pHdr->portsrc == 0x00 /* Driver */ || pHdr->portsrc == 0x10 /* FMM */ )) { uiState = STATE_SECTION_PROV; } else { DEBUG(1, "FT1000:download:Download error: Bad Port IDs in Pseudo Record\n"); DEBUG(1, "\t Port Source = 0x%2.2x\n", pHdr->portsrc); DEBUG(1, "\t Port Destination = 0x%2.2x\n", pHdr->portdest); Status = FAILURE; } break; case STATE_SECTION_PROV: pHdr = (struct pseudo_hdr *) pUcFile; if (pHdr->checksum == hdr_checksum(pHdr)) { if (pHdr->portdest != 0x80 /* Dsp OAM */ ) { uiState = STATE_DONE_PROV; break; } usHdrLength = ntohs(pHdr->length); /* Byte length for PROV records */ // Get buffer for provisioning data pbuffer = kmalloc((usHdrLength + sizeof(struct pseudo_hdr)), GFP_ATOMIC); if (pbuffer) { memcpy(pbuffer, (void *)pUcFile, (u32) (usHdrLength + sizeof(struct pseudo_hdr))); // link provisioning data pprov_record = kmalloc(sizeof(struct prov_record), GFP_ATOMIC); if (pprov_record) { pprov_record->pprov_data = pbuffer; list_add_tail(&pprov_record-> list, &info->prov_list); // Move to next entry if available pUcFile = (u8 *) ((unsigned long) pUcFile + (unsigned long) ((usHdrLength + 1) & 0xFFFFFFFE) + sizeof(struct pseudo_hdr)); if ((unsigned long) (pUcFile) - (unsigned long) (pFileStart) >= (unsigned long) FileLength) { uiState = STATE_DONE_FILE; } } else { kfree(pbuffer); Status = FAILURE; } } else { Status = FAILURE; } } else { /* Checksum did not compute */ Status = FAILURE; } break; case STATE_DONE_PROV: uiState = STATE_DONE_FILE; break; default: Status = FAILURE; break; } /* End Switch */ } /* End while */ return Status; }