bbcfileif.c 8.22 KB
/*
 * BBC File Interface
 *
 * Simulate a BB Cartridge using a disk file.  The file is an array
 * of flash blocks, each one consisting of 16KB of data followed
 * immediately by 16 bytes of spare area with no other delimiters
 * or padding.
 * 
 * At present, the ECC support is not simulated.  Only the links
 * and status byte of the spare area are significant.
 *
 * Bad blocks behaviors are simulated by supplying a bad block list
 * in the environment variables (see new_file routine at the
 * bottom of this file for details).
 */

#include "bbclocal.h"
#include "stdio.h"

#define _BBC_FILE_BLK_SIZE (BB_FL_BLOCK_SIZE + BB_FL_SPARE_SIZE)

/*
 * Bit masks for bad block emulation
 */
#define	BBC_EM_MK_BAD		1
#define	BBC_EM_RD_DBE		2
#define	BBC_EM_WR_ERR		4
#define	BBC_EM_WR_BAD		8
#define	BBC_EM_CM_ERR		16

static char *bbcf_bad_blks = NULL;

static void
__bbc_file_close(void *f)
{
    bbc_hand *hp = f;
    FILE *filep = (FILE *)hp->bh_ufd;

    fsync(fileno(filep));
    fclose(filep);
    if (bbcf_bad_blks != NULL) {
	free(bbcf_bad_blks);
	bbcf_bad_blks = NULL;
    }
}

static int
__bbc_file_setled(void *f, int ledmask)
{
    return BBC_OK;
}

static int
__bbc_file_settime(void *f, time_t curtime)
{
    return BBC_NODEV;
}

/*
 * Read blocks from the file simulating the flash cartridge.
 * Each block is 16KB of data, followed by 16 bytes of spare area.
 */
static int
__bbc_file_read_blocks(void* f, u32 blk, int nblks, void* data, void* spare)
{
    bbc_hand *hp = f;
    FILE *filep = (FILE *) hp->bh_ufd;
    unsigned char *sparep = (unsigned char *)spare;
    int i;

    if (spare == NULL)
	return BBC_BADARG;

    memset(spare, 0xff, nblks * BB_FL_SPARE_SIZE);

    if (fseek(filep, blk * _BBC_FILE_BLK_SIZE, SEEK_SET) < 0) {
        BBC_LOG(MSG_ERR, "fseek fails (errno %d)\n", errno);
	return BBC_ERROR;
    }

    for (i = 0; i < nblks; i++) {
	if (fread(data, 1, BB_FL_BLOCK_SIZE, filep) < BB_FL_BLOCK_SIZE) {
            BBC_LOG(MSG_ERR, "fread fails (errno %d)\n", errno);
	    return BBC_ERROR;
	}
	if (fread(spare, 1, BB_FL_SPARE_SIZE, filep) < BB_FL_SPARE_SIZE) {
            BBC_LOG(MSG_ERR, "fread fails (errno %d)\n", errno);
	    return BBC_ERROR;
	}
	/*
	 * Check if this block is supposed to behave badly
	 */
	if (bbcf_bad_blks != NULL) {
            if (bbcf_bad_blks[blk+i] & BBC_EM_MK_BAD)
	        /* Marked bad by MFG (more than one zero bit in status byte) */
	        *((char *)spare + BB_FL_BLOCK_STATUS_OFF) = 0xFA;
	    if (bbcf_bad_blks[blk+i] & BBC_EM_RD_DBE)
		/* Read with DBE */
		return BBC_DATAERR;
	    if (bbcf_bad_blks[blk+i] & BBC_EM_CM_ERR)
		/* Communication error (failure other than DATAERR) */
		return BBC_ERROR;
        }
	(char *) data += BB_FL_BLOCK_SIZE;
	(char *) spare += BB_FL_SPARE_SIZE;
    }

    for (i = 0; i < nblks; i++) {
	if (__bbc_zerobits(sparep[BB_FL_SPARE_SIZE*i + BB_FL_BLOCK_STATUS_OFF]) > 1) {
	    BBC_LOG(MSG_ERR, "Blk %ld marked bad\n", blk+i);
	    return BBC_BADBLK;
	}
    }
    return BBC_OK;
}

static unsigned char blkbuf[BB_FL_BLOCK_SIZE];

/*
 * Write blocks to the file simulating the flash cartridge.
 * Each block is 16KB of data, followed by 16 bytes of spare area.
 */
static int
__bbc_file_write_blocks(void* f, u32 blk, int nblks, const void* data, void* spare)
{
    bbc_hand *hp = f;
    FILE *filep = (FILE *) hp->bh_ufd;
    char locspare[BB_FL_SPARE_SIZE*4];
    int i;

    if (spare == NULL) {
	memset(locspare, 0xff, sizeof(locspare));
	spare = locspare;
    }

    if (fseek(filep, blk * _BBC_FILE_BLK_SIZE, SEEK_SET) < 0) {
        BBC_LOG(MSG_ERR, "fseek fails (errno %d)\n", errno);
	return BBC_ERROR;
    }

    for (i = 0; i < nblks; i++) {
	/* Copy the data, in case we need to corrupt it */
	memcpy(blkbuf, (const unsigned char *)data, sizeof blkbuf);
	/*
	 * Check if this block is supposed to behave badly
	 */
	if (bbcf_bad_blks != NULL) {
            if (bbcf_bad_blks[blk+i] & BBC_EM_CM_ERR)
	        /* Comm error */
		return BBC_ERROR;
	    if (bbcf_bad_blks[blk+i] & BBC_EM_MK_BAD)
	        /* Marked bad by MFG */
		return BBC_DATAERR;
	    if (bbcf_bad_blks[blk+i] & BBC_EM_WR_ERR)
	        /* Write err */
		return BBC_DATAERR;
	    if (bbcf_bad_blks[blk+i] & BBC_EM_WR_BAD) {
		unsigned char *cp = blkbuf + 27;
		if (*cp == 0x0)
		    *cp = 0x42;
		else
		    *cp = 0x0;
	    }
        }
	if (fwrite(blkbuf, 1, BB_FL_BLOCK_SIZE, filep) < BB_FL_BLOCK_SIZE) {
            BBC_LOG(MSG_ERR, "fwrite fails (errno %d)\n", errno);
	    return BBC_ERROR;
	}
	if (fwrite(spare, 1, BB_FL_SPARE_SIZE, filep) < BB_FL_SPARE_SIZE) {
            BBC_LOG(MSG_ERR, "fwrite fails (errno %d)\n", errno);
	    return BBC_ERROR;
	}
	(char *) data += BB_FL_BLOCK_SIZE;
	(char *) spare += BB_FL_SPARE_SIZE;
	/*
	 * If rewriting a Read DBE block, clear the read error bit
	 */
	if (bbcf_bad_blks != NULL) {
	    if (bbcf_bad_blks[blk+i] & BBC_EM_RD_DBE)
		/* Read with DBE, but write ok, so clear the read err */
	        bbcf_bad_blks[blk+i] &= ~BBC_EM_RD_DBE;
        }
    }

    return BBC_OK;
}

int
__bbc_new_file(bbc_hand *hp, const char *device)
{
    flashif_t* f;
    FILE *filep;
    struct stat sb;
    char *badlist, *tok;
    int blk;

    BBC_LOG(MSG_INFO, "new_file %s\n", device);
    if ((filep = fopen(device, "r+")) == NULL) {
        BBC_LOG(MSG_ERR, "fopen %s fails (errno %d)\n", device, errno);
	return BBC_NODEV;
    }
    hp->bh_ufd = (int) filep;

    hp->bh_card_present = 1;
    if (fstat(fileno(filep), &sb) < 0) {
        BBC_LOG(MSG_ERR, "fstat %s fails (errno %d)\n", device, errno);
	(void) fclose(filep);
	return BBC_NODEV;
    }
    hp->bh_cardsize = sb.st_size / _BBC_FILE_BLK_SIZE;
    BBC_LOG(MSG_INFO, "%s: nblks %d\n", device, hp->bh_cardsize);

    if (bbcf_bad_blks != NULL) {
	free(bbcf_bad_blks);
	bbcf_bad_blks = NULL;
    }

    /*
     * Import the bad block information and convert it into a
     * bad block map.  The code supports three kinds of bad
     * behavior:
     *
     *   Marked BAD by manufacturer
     *   Reads with DBE (but will stop this after a write)
     *   Gives hard error on write (and still reads with DBE)
     */
    if ((badlist = getenv("BBC_BAD_BLKS")) != NULL) {
        if ((bbcf_bad_blks = malloc(hp->bh_cardsize)) == NULL) {
            BBC_LOG(MSG_ERR, "malloc fails\n");
	    fclose(filep);
	    return BBC_TOOMANY;
        }
	memset(bbcf_bad_blks, 0, hp->bh_cardsize);
	tok = strtok(badlist, " \t,;");
	while (tok != NULL) {
	    blk = atoi(tok);
	    bbcf_bad_blks[blk] |= BBC_EM_MK_BAD;
            BBC_LOG(MSG_WARNING, "blk %d marked bad by MFG\n", blk);
	    tok = strtok(NULL, " \t,;");
	}
        if ((badlist = getenv("BBC_READ_DBE_BLKS")) != NULL) {
	    tok = strtok(badlist, " \t,;");
	    while (tok != NULL) {
	        blk = atoi(tok);
	        bbcf_bad_blks[blk] |= BBC_EM_RD_DBE;
                BBC_LOG(MSG_WARNING, "blk %d reads with DBE\n", blk);
	        tok = strtok(NULL, " \t,;");
	    }
	}
        if ((badlist = getenv("BBC_WRITE_ERR_BLKS")) != NULL) {
	    tok = strtok(badlist, " \t,;");
	    while (tok != NULL) {
	        blk = atoi(tok);
	        bbcf_bad_blks[blk] |= BBC_EM_WR_ERR;
                BBC_LOG(MSG_WARNING, "blk %d gives err on write\n", blk);
	        tok = strtok(NULL, " \t,;");
	    }
	}
        if ((badlist = getenv("BBC_WRITE_FLAKY_BLKS")) != NULL) {
	    tok = strtok(badlist, " \t,;");
	    while (tok != NULL) {
	        blk = atoi(tok);
	        bbcf_bad_blks[blk] |= BBC_EM_WR_BAD;
                BBC_LOG(MSG_WARNING, "blk %d writes bad corrupted data\n", blk);
	        tok = strtok(NULL, " \t,;");
	    }
	}
        if ((badlist = getenv("BBC_COMM_ERR_BLKS")) != NULL) {
	    tok = strtok(badlist, " \t,;");
	    while (tok != NULL) {
	        blk = atoi(tok);
	        bbcf_bad_blks[blk] |= BBC_EM_CM_ERR;
                BBC_LOG(MSG_WARNING, "blk %d gives comm error\n", blk);
	        tok = strtok(NULL, " \t,;");
	    }
	}
    }

    if ((f = malloc(sizeof(flashif_t))) == NULL) {
        BBC_LOG(MSG_ERR, "malloc fails\n");
	fclose(filep);
	if (bbcf_bad_blks != NULL) {
	    free(bbcf_bad_blks);
	    bbcf_bad_blks = NULL;
	}
	return BBC_TOOMANY;
    }
    hp->bh_flashif = f;
    memset(f, 0, sizeof(flashif_t));
    f->f = (void *)hp;
    f->close = __bbc_file_close;
    f->setled = __bbc_file_setled;
    f->settime = __bbc_file_settime;
    f->read_blocks = __bbc_file_read_blocks;
    f->write_blocks = __bbc_file_write_blocks;
    return BBC_OK;
}