/* * Floppy Disk Cache (Atari ST) * * Copyright 1988, Eric Gisin * this program may be copied as long as this copyright notice is retained. * the author will not be liable for any damages resulting from the use of this program. * * Usage: cache [drive][size] * can be loaded twice to handle two drives. * * The cache is based on paging and page replacement techniques * used in virtual memory systems. The replacement algorithm is * similiar to "not frequently used" (NFU). * * each sector of the disk has a (struct) Cache element * that contains a pointer to a cache buffer, * and an "average usage" value. The average is a combination * of long term usage and short term usage (somethink like 4bsd's load avg). * there are also averages for total IO, read, write, hits, and reclaims. * an average is increased for each reference (read/write), * and all averages are decreased (by a percentage) every 100 accesses. * the reference function is: avg += A_INC; A_INC = 16; * the update function is: avg -= 1/8*avg; * for these parameters, the total avg will tend between 12800 and 14400. */ #include #include "cache.h" /* cache.c can compile with DLIBS or MWC */ #ifndef MWC #define DLIBS #endif #ifdef MJC #define ASM 1 /* inline assembler */ #endif #define reg register #ifdef OK typedef long (*Func)(); /* function returning long */ #else typedef char * Func; /* good enough */ #endif /* define system variables */ #define bios_rw (*(Func*)0x000476) /* bios read/write routine */ #define bios_mc (*(Func*)0x00047E) /* bios media change routine */ #ifdef OK Func old_rw; Func cur_mc; #else long (*old_rw)(); /* previous rwabs routine */ long (*cur_mc)(); /* current mediach routine */ #endif /* per-device data */ int c_dev = 0; /* cached device, A: */ int disable; /* flag to disable */ short stats [STATS]; /* global averages */ Cache cache [NSECT]; /* per sector averages and cbufs indices */ short csize; /* size of cache in sectors */ Sector* cbufs; /* cached sector buffers */ short cycle; /* count from 100 to 0 */ short nfree; /* next free cache sector */ #ifdef DLIBS extern char *_base, *_break; /* program basepage, heap break */ long _STKSIZ = -2L; /* grab all free memory for heap */ #else /* MWC */ #include #define _base ((char *)BP) #define _break _stack /* MWC combines _STKSIZ and _break as _stack */ char *_break = (char *)300*1024L; /* grab 300K for heap */ #endif char header [] = "ST Floppy Disk Cache (by Eric Gisin)\r\n"; /* * setup bios disk IO routines, terminate and stay resident. */ main(argc, argv) int argc; char **argv; { long stack; extern long cache_rw(); extern char *sbrk(/*size_t n*/); Cconws(header); if (argc > 1) { char *arg = argv[1]; if ('a' <= *arg && *arg <= 'z') c_dev = *arg++ - 'a'; if ('A' <= *arg && *arg <= 'Z') c_dev = *arg++ - 'A'; csize = atoi(arg) * 1024/sizeof(Sector); } if (csize < 100) csize = 100; /* 50K is minimum */ #ifndef DEBUG if (Rwabs(CSTAT, (char*)NULL, 0, 0, c_dev) != NULL) { /* already loaded */ Cconws("Cache already loaded\r\n"); return 0; } #endif #if 0 cbufs = (Sector*) sbrk(csize * sizeof(Sector)); #else /* avoid loading long multiply */ cbufs = (Sector*) sbrk((size_t)&((Sector*)NULL)[csize]); #endif if (cbufs == NULL) { Cconws("Not enough memory\r\n"); Pterm(-1); } /* set up our routines for bios rwabs */ stack = Super(NULL); /* super mode */ old_rw = bios_rw; bios_rw = cache_rw; cur_mc = bios_mc; Super(stack); /* user mode */ cache_init(); Ptermres(_break - _base, 0); /* terminate, stay resident */ } /* clear the cache */ cache_init() { reg int i; cycle = CYCLE; nfree = 0; for (i = 0; i < STATS; i ++) stats[i] = 0; for (i = 0; i < NSECT; i ++) { cache[i].avg = 0; cache[i].buf = NOBUF; } return 0; } /* our BIOS rwabs routine */ long cache_rw(func, addr, count, sect, dev) int func; /* IO function */ Sector* addr; /* buffer address */ int count; /* sector count */ int sect; /* sector number */ int dev; /* disk drive */ { reg int s; reg int i; int n; long l; if (dev != c_dev || disable) return (*old_rw)(func, addr, count, sect, dev); /* recognize magic function for cstat */ if (func == CSTAT) return (sect==0) ? stats : cache; if (func == CINIT) return cache_init(); /* check for media change */ /* floppy rwabs will return -14 if real media change */ if ((*cur_mc)(dev) != 0) { l = (*old_rw)(0, 0xFC0000, 0, 0, dev); /* dummy read */ if (l != 0) { cache_init(); return l; /* always -14 (media changed)? */ } } if (sect < 0 || count < 0 || sect+count < 0) return -5; /* sector out of range */ /* update cache averages */ for (i = 0; i < count; i ++) { s = sect + i; stats[TOTAL] += A_INC; stats[((func&1)==0) ? READS : WRITES] += A_INC; if (s < NSECT) cache[s].avg += A_INC; if (-- cycle < 0) { cycle = CYCLE; update_averages(); } } /* copy cache to read buffer */ if ((func&1)==0 && sect>0) { for (i = 0; i < count; i ++) { s = sect + i; n = cache[s].buf; if (n == NOBUF) break; copy_sector(&cbufs[n], addr++); stats[HITS] += A_INC; } sect += i; count -= i; } /* do BIOS IO */ l = (*old_rw)(func, addr, count, sect, dev); if (l != 0) return l; /* update cache from IO buffer */ for (i = 0; i < count; i ++) { s = sect + i; n = cache[s].buf; if (n == NOBUF) get_free(s); n = cache[s].buf; if (n != NOBUF) copy_sector(addr + i, &cbufs[n]); } return 0; } /* * update averages. * this is called once per N accesses. */ update_averages() { reg int i; int n; for (i = 0; i < STATS; ++ i) { stats[i] -= stats[i]+7 >> 3; } for (i = 0; i < NSECT; ++ i) { cache[i].avg -= cache[i].avg+7 >> 3; n = cache[i].buf; if (!(n == NOBUF || 0<=n && n"); } } } /* * get cache block with lowest avg less than requested block. * somewhat inefficient, but should not be called often. */ get_free(c) int c; /* sector */ { reg int s; reg Cache * p; reg Cache * m; if (nfree < csize) { cache[c].buf = nfree++; return; } /* find sector with a buffer and lowest avg */ p = &cache[0]; m = &cache[c]; for (s = 0; s < NSECT; s ++, p ++) if (p->buf != NOBUF && p->avg <= m->avg) m = p; /* if that sector has lower avg than requested sector, reclaim */ if (m->avg < cache[c].avg) { cache[c].buf = m->buf; m->buf = NOBUF; stats[RECLMS] += A_INC; } } copy_sector(s, d) Sector *s; Sector *d; { int i; reg char *dp = d->data; /* A5 in MJC */ reg char *sp = s->data; /* A4 in MJC */ if ((s&1) | (d&1)) { /* odd address! */ i = sizeof(Sector); while (--i >= 0) *dp++ = *sp++; } else { #ifndef ASM /* usually generates loop: move.w (A0)+, (A1)+; dbra D0, loop */ *d = *s; #else /* this is twice as fast as above */ sizeof(Sector)/sizeof(long)/4 - 1; /* result to D0 */ /* isn't machine language fun! loop: */ asm(= 10972); /* move.l (A4)+, (A5)+ */ asm(= 10972); /* move.l (A4)+, (A5)+ */ asm(= 10972); /* move.l (A4)+, (A5)+ */ asm(= 10972); /* move.l (A4)+, (A5)+ */ asm(= 20936 = -10); /* dbra D0, loop */ #endif } } /* allocate n bytes from heap (stack segment). NULL on error */ char *sbrk(n) size_t n; { extern char *_break; /* current heap break */ char *p = _break; int dummy; /* &dummy is almost SP */ _break += n; if (_break+256 > (char*)&dummy) { _break = p; return NULL; } return p; } #if 0 int brk(p) char *p; /* new break address */ { int dummy; /* &dummy is almost SP */ extern char *_break; /* current break address */ extern char *end; /* initial _break; */ if (p < end || p+256 > (char *)&dummy) return -1; _break = p; return 0; } #endif