/* $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 = kzalloc(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; 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 */