| /* $Id: hysdn_boot.c,v 1.4.6.4 2001/09/23 22:24:54 kai Exp $ |
| * |
| * Linux driver for HYSDN cards |
| * specific routines for booting and pof handling |
| * |
| * Author Werner Cornelius (werner@titro.de) for Hypercope GmbH |
| * Copyright 1999 by Werner Cornelius (werner@titro.de) |
| * |
| * This software may be used and distributed according to the terms |
| * of the GNU General Public License, incorporated herein by reference. |
| * |
| */ |
| |
| #include <linux/vmalloc.h> |
| #include <linux/slab.h> |
| #include <asm/uaccess.h> |
| |
| #include "hysdn_defs.h" |
| #include "hysdn_pof.h" |
| |
| /********************************/ |
| /* defines for pof read handler */ |
| /********************************/ |
| #define POF_READ_FILE_HEAD 0 |
| #define POF_READ_TAG_HEAD 1 |
| #define POF_READ_TAG_DATA 2 |
| |
| /************************************************************/ |
| /* definition of boot specific data area. This data is only */ |
| /* needed during boot and so allocated dynamically. */ |
| /************************************************************/ |
| struct boot_data { |
| unsigned short Cryptor; /* for use with Decrypt function */ |
| unsigned short Nrecs; /* records remaining in file */ |
| unsigned char pof_state;/* actual state of read handler */ |
| unsigned char is_crypted;/* card data is crypted */ |
| int BufSize; /* actual number of bytes bufferd */ |
| int last_error; /* last occurred error */ |
| unsigned short pof_recid;/* actual pof recid */ |
| unsigned long pof_reclen;/* total length of pof record data */ |
| unsigned long pof_recoffset;/* actual offset inside pof record */ |
| union { |
| unsigned char BootBuf[BOOT_BUF_SIZE];/* buffer as byte count */ |
| tPofRecHdr PofRecHdr; /* header for actual record/chunk */ |
| tPofFileHdr PofFileHdr; /* header from POF file */ |
| tPofTimeStamp PofTime; /* time information */ |
| } buf; |
| }; |
| |
| /*****************************************************/ |
| /* start decryption of successive POF file chuncks. */ |
| /* */ |
| /* to be called at start of POF file reading, */ |
| /* before starting any decryption on any POF record. */ |
| /*****************************************************/ |
| static void |
| StartDecryption(struct boot_data *boot) |
| { |
| boot->Cryptor = CRYPT_STARTTERM; |
| } /* StartDecryption */ |
| |
| |
| /***************************************************************/ |
| /* decrypt complete BootBuf */ |
| /* NOTE: decryption must be applied to all or none boot tags - */ |
| /* to HI and LO boot loader and (all) seq tags, because */ |
| /* global Cryptor is started for whole POF. */ |
| /***************************************************************/ |
| static void |
| DecryptBuf(struct boot_data *boot, int cnt) |
| { |
| unsigned char *bufp = boot->buf.BootBuf; |
| |
| while (cnt--) { |
| boot->Cryptor = (boot->Cryptor >> 1) ^ ((boot->Cryptor & 1U) ? CRYPT_FEEDTERM : 0); |
| *bufp++ ^= (unsigned char)boot->Cryptor; |
| } |
| } /* DecryptBuf */ |
| |
| /********************************************************************************/ |
| /* pof_handle_data executes the required actions dependent on the active record */ |
| /* id. If successful 0 is returned, a negative value shows an error. */ |
| /********************************************************************************/ |
| static int |
| pof_handle_data(hysdn_card * card, int datlen) |
| { |
| struct boot_data *boot = card->boot; /* pointer to boot specific data */ |
| long l; |
| unsigned char *imgp; |
| int img_len; |
| |
| /* handle the different record types */ |
| switch (boot->pof_recid) { |
| |
| case TAG_TIMESTMP: |
| if (card->debug_flags & LOG_POF_RECORD) |
| hysdn_addlog(card, "POF created %s", boot->buf.PofTime.DateTimeText); |
| break; |
| |
| case TAG_CBOOTDTA: |
| DecryptBuf(boot, datlen); /* we need to encrypt the buffer */ |
| case TAG_BOOTDTA: |
| if (card->debug_flags & LOG_POF_RECORD) |
| hysdn_addlog(card, "POF got %s len=%d offs=0x%lx", |
| (boot->pof_recid == TAG_CBOOTDTA) ? "CBOOTDATA" : "BOOTDTA", |
| datlen, boot->pof_recoffset); |
| |
| if (boot->pof_reclen != POF_BOOT_LOADER_TOTAL_SIZE) { |
| boot->last_error = EPOF_BAD_IMG_SIZE; /* invalid length */ |
| return (boot->last_error); |
| } |
| imgp = boot->buf.BootBuf; /* start of buffer */ |
| img_len = datlen; /* maximum length to transfer */ |
| |
| l = POF_BOOT_LOADER_OFF_IN_PAGE - |
| (boot->pof_recoffset & (POF_BOOT_LOADER_PAGE_SIZE - 1)); |
| if (l > 0) { |
| /* buffer needs to be truncated */ |
| imgp += l; /* advance pointer */ |
| img_len -= l; /* adjust len */ |
| } |
| /* at this point no special handling for data wrapping over buffer */ |
| /* is necessary, because the boot image always will be adjusted to */ |
| /* match a page boundary inside the buffer. */ |
| /* The buffer for the boot image on the card is filled in 2 cycles */ |
| /* first the 1024 hi-words are put in the buffer, then the low 1024 */ |
| /* word are handled in the same way with different offset. */ |
| |
| if (img_len > 0) { |
| /* data available for copy */ |
| if ((boot->last_error = |
| card->writebootimg(card, imgp, |
| (boot->pof_recoffset > POF_BOOT_LOADER_PAGE_SIZE) ? 2 : 0)) < 0) |
| return (boot->last_error); |
| } |
| break; /* end of case boot image hi/lo */ |
| |
| case TAG_CABSDATA: |
| DecryptBuf(boot, datlen); /* we need to encrypt the buffer */ |
| case TAG_ABSDATA: |
| if (card->debug_flags & LOG_POF_RECORD) |
| hysdn_addlog(card, "POF got %s len=%d offs=0x%lx", |
| (boot->pof_recid == TAG_CABSDATA) ? "CABSDATA" : "ABSDATA", |
| datlen, boot->pof_recoffset); |
| |
| if ((boot->last_error = card->writebootseq(card, boot->buf.BootBuf, datlen) < 0)) |
| return (boot->last_error); /* error writing data */ |
| |
| if (boot->pof_recoffset + datlen >= boot->pof_reclen) |
| return (card->waitpofready(card)); /* data completely spooled, wait for ready */ |
| |
| break; /* end of case boot seq data */ |
| |
| default: |
| if (card->debug_flags & LOG_POF_RECORD) |
| hysdn_addlog(card, "POF got data(id=0x%lx) len=%d offs=0x%lx", boot->pof_recid, |
| datlen, boot->pof_recoffset); |
| |
| break; /* simply skip record */ |
| } /* switch boot->pof_recid */ |
| |
| return (0); |
| } /* pof_handle_data */ |
| |
| |
| /******************************************************************************/ |
| /* pof_write_buffer is called when the buffer has been filled with the needed */ |
| /* number of data bytes. The number delivered is additionally supplied for */ |
| /* verification. The functions handles the data and returns the needed number */ |
| /* of bytes for the next action. If the returned value is 0 or less an error */ |
| /* occurred and booting must be aborted. */ |
| /******************************************************************************/ |
| int |
| pof_write_buffer(hysdn_card * card, int datlen) |
| { |
| struct boot_data *boot = card->boot; /* pointer to boot specific data */ |
| |
| if (!boot) |
| return (-EFAULT); /* invalid call */ |
| if (boot->last_error < 0) |
| return (boot->last_error); /* repeated error */ |
| |
| if (card->debug_flags & LOG_POF_WRITE) |
| hysdn_addlog(card, "POF write: got %d bytes ", datlen); |
| |
| switch (boot->pof_state) { |
| case POF_READ_FILE_HEAD: |
| if (card->debug_flags & LOG_POF_WRITE) |
| hysdn_addlog(card, "POF write: checking file header"); |
| |
| if (datlen != sizeof(tPofFileHdr)) { |
| boot->last_error = -EPOF_INTERNAL; |
| break; |
| } |
| if (boot->buf.PofFileHdr.Magic != TAGFILEMAGIC) { |
| boot->last_error = -EPOF_BAD_MAGIC; |
| break; |
| } |
| /* Setup the new state and vars */ |
| boot->Nrecs = (unsigned short)(boot->buf.PofFileHdr.N_PofRecs); /* limited to 65535 */ |
| boot->pof_state = POF_READ_TAG_HEAD; /* now start with single tags */ |
| boot->last_error = sizeof(tPofRecHdr); /* new length */ |
| break; |
| |
| case POF_READ_TAG_HEAD: |
| if (card->debug_flags & LOG_POF_WRITE) |
| hysdn_addlog(card, "POF write: checking tag header"); |
| |
| if (datlen != sizeof(tPofRecHdr)) { |
| boot->last_error = -EPOF_INTERNAL; |
| break; |
| } |
| boot->pof_recid = boot->buf.PofRecHdr.PofRecId; /* actual pof recid */ |
| boot->pof_reclen = boot->buf.PofRecHdr.PofRecDataLen; /* total length */ |
| boot->pof_recoffset = 0; /* no starting offset */ |
| |
| if (card->debug_flags & LOG_POF_RECORD) |
| hysdn_addlog(card, "POF: got record id=0x%lx length=%ld ", |
| boot->pof_recid, boot->pof_reclen); |
| |
| boot->pof_state = POF_READ_TAG_DATA; /* now start with tag data */ |
| if (boot->pof_reclen < BOOT_BUF_SIZE) |
| boot->last_error = boot->pof_reclen; /* limit size */ |
| else |
| boot->last_error = BOOT_BUF_SIZE; /* maximum */ |
| |
| if (!boot->last_error) { /* no data inside record */ |
| boot->pof_state = POF_READ_TAG_HEAD; /* now start with single tags */ |
| boot->last_error = sizeof(tPofRecHdr); /* new length */ |
| } |
| break; |
| |
| case POF_READ_TAG_DATA: |
| if (card->debug_flags & LOG_POF_WRITE) |
| hysdn_addlog(card, "POF write: getting tag data"); |
| |
| if (datlen != boot->last_error) { |
| boot->last_error = -EPOF_INTERNAL; |
| break; |
| } |
| if ((boot->last_error = pof_handle_data(card, datlen)) < 0) |
| return (boot->last_error); /* an error occurred */ |
| boot->pof_recoffset += datlen; |
| if (boot->pof_recoffset >= boot->pof_reclen) { |
| boot->pof_state = POF_READ_TAG_HEAD; /* now start with single tags */ |
| boot->last_error = sizeof(tPofRecHdr); /* new length */ |
| } else { |
| if (boot->pof_reclen - boot->pof_recoffset < BOOT_BUF_SIZE) |
| boot->last_error = boot->pof_reclen - boot->pof_recoffset; /* limit size */ |
| else |
| boot->last_error = BOOT_BUF_SIZE; /* maximum */ |
| } |
| break; |
| |
| default: |
| boot->last_error = -EPOF_INTERNAL; /* unknown state */ |
| break; |
| } /* switch (boot->pof_state) */ |
| |
| return (boot->last_error); |
| } /* pof_write_buffer */ |
| |
| |
| /*******************************************************************************/ |
| /* pof_write_open is called when an open for boot on the cardlog device occurs. */ |
| /* The function returns the needed number of bytes for the next operation. If */ |
| /* the returned number is less or equal 0 an error specified by this code */ |
| /* occurred. Additionally the pointer to the buffer data area is set on success */ |
| /*******************************************************************************/ |
| int |
| pof_write_open(hysdn_card * card, unsigned char **bufp) |
| { |
| struct boot_data *boot; /* pointer to boot specific data */ |
| |
| if (card->boot) { |
| if (card->debug_flags & LOG_POF_OPEN) |
| hysdn_addlog(card, "POF open: already opened for boot"); |
| return (-ERR_ALREADY_BOOT); /* boot already active */ |
| } |
| /* error no mem available */ |
| if (!(boot = kmalloc(sizeof(struct boot_data), GFP_KERNEL))) { |
| if (card->debug_flags & LOG_MEM_ERR) |
| hysdn_addlog(card, "POF open: unable to allocate mem"); |
| return (-EFAULT); |
| } |
| card->boot = boot; |
| card->state = CARD_STATE_BOOTING; |
| memset(boot, 0, sizeof(struct boot_data)); |
| |
| card->stopcard(card); /* first stop the card */ |
| if (card->testram(card)) { |
| if (card->debug_flags & LOG_POF_OPEN) |
| hysdn_addlog(card, "POF open: DPRAM test failure"); |
| boot->last_error = -ERR_BOARD_DPRAM; |
| card->state = CARD_STATE_BOOTERR; /* show boot error */ |
| return (boot->last_error); |
| } |
| boot->BufSize = 0; /* Buffer is empty */ |
| boot->pof_state = POF_READ_FILE_HEAD; /* read file header */ |
| StartDecryption(boot); /* if POF File should be encrypted */ |
| |
| if (card->debug_flags & LOG_POF_OPEN) |
| hysdn_addlog(card, "POF open: success"); |
| |
| *bufp = boot->buf.BootBuf; /* point to buffer */ |
| return (sizeof(tPofFileHdr)); |
| } /* pof_write_open */ |
| |
| /********************************************************************************/ |
| /* pof_write_close is called when an close of boot on the cardlog device occurs. */ |
| /* The return value must be 0 if everything has happened as desired. */ |
| /********************************************************************************/ |
| int |
| pof_write_close(hysdn_card * card) |
| { |
| struct boot_data *boot = card->boot; /* pointer to boot specific data */ |
| |
| if (!boot) |
| return (-EFAULT); /* invalid call */ |
| |
| card->boot = NULL; /* no boot active */ |
| kfree(boot); |
| |
| if (card->state == CARD_STATE_RUN) |
| card->set_errlog_state(card, 1); /* activate error log */ |
| |
| if (card->debug_flags & LOG_POF_OPEN) |
| hysdn_addlog(card, "POF close: success"); |
| |
| return (0); |
| } /* pof_write_close */ |
| |
| /*********************************************************************************/ |
| /* EvalSysrTokData checks additional records delivered with the Sysready Message */ |
| /* when POF has been booted. A return value of 0 is used if no error occurred. */ |
| /*********************************************************************************/ |
| int |
| EvalSysrTokData(hysdn_card *card, unsigned char *cp, int len) |
| { |
| u_char *p; |
| u_char crc; |
| |
| if (card->debug_flags & LOG_POF_RECORD) |
| hysdn_addlog(card, "SysReady Token data length %d", len); |
| |
| if (len < 2) { |
| hysdn_addlog(card, "SysReady Token Data to short"); |
| return (1); |
| } |
| for (p = cp, crc = 0; p < (cp + len - 2); p++) |
| if ((crc & 0x80)) |
| crc = (((u_char) (crc << 1)) + 1) + *p; |
| else |
| crc = ((u_char) (crc << 1)) + *p; |
| crc = ~crc; |
| if (crc != *(cp + len - 1)) { |
| hysdn_addlog(card, "SysReady Token Data invalid CRC"); |
| return (1); |
| } |
| len--; /* don't check CRC byte */ |
| while (len > 0) { |
| |
| if (*cp == SYSR_TOK_END) |
| return (0); /* End of Token stream */ |
| |
| if (len < (*(cp + 1) + 2)) { |
| hysdn_addlog(card, "token 0x%x invalid length %d", *cp, *(cp + 1)); |
| return (1); |
| } |
| switch (*cp) { |
| case SYSR_TOK_B_CHAN: /* 1 */ |
| if (*(cp + 1) != 1) |
| return (1); /* length invalid */ |
| card->bchans = *(cp + 2); |
| break; |
| |
| case SYSR_TOK_FAX_CHAN: /* 2 */ |
| if (*(cp + 1) != 1) |
| return (1); /* length invalid */ |
| card->faxchans = *(cp + 2); |
| break; |
| |
| case SYSR_TOK_MAC_ADDR: /* 3 */ |
| if (*(cp + 1) != 6) |
| return (1); /* length invalid */ |
| memcpy(card->mac_addr, cp + 2, 6); |
| break; |
| |
| default: |
| hysdn_addlog(card, "unknown token 0x%02x length %d", *cp, *(cp + 1)); |
| break; |
| } |
| len -= (*(cp + 1) + 2); /* adjust len */ |
| cp += (*(cp + 1) + 2); /* and pointer */ |
| } |
| |
| hysdn_addlog(card, "no end token found"); |
| return (1); |
| } /* EvalSysrTokData */ |