| #include <u.h> | 
 | #include <libc.h> | 
 | #include <diskfs.h> | 
 |  | 
 | /* | 
 |  * Disk cache.  Caches by offset, so higher levels have  | 
 |  * to deal with alignment issues (if we get asked for the | 
 |  * blocks at offsets 0 and 1, we'll do two reads). | 
 |  */ | 
 |  | 
 | typedef struct DiskCache DiskCache; | 
 | typedef struct DiskCacheBlock DiskCacheBlock; | 
 |  | 
 | struct DiskCache | 
 | { | 
 | 	Disk disk; | 
 | 	Disk *subdisk; | 
 | 	DiskCacheBlock **h; | 
 | 	DiskCacheBlock *lruhead; | 
 | 	DiskCacheBlock *lrutail; | 
 | 	int nhash; | 
 | 	int blocksize; | 
 | 	Lock lk; | 
 | }; | 
 |  | 
 | struct DiskCacheBlock | 
 | { | 
 | 	Block block; | 
 | 	Block *subblock; | 
 | 	Lock lk; | 
 | 	int ref; | 
 | 	DiskCache *dc; | 
 | 	DiskCacheBlock *next; | 
 | 	DiskCacheBlock *lrunext; | 
 | 	DiskCacheBlock *lruprev; | 
 | 	u64int offset; | 
 | }; | 
 |  | 
 | static void | 
 | addtohash(DiskCache *d, DiskCacheBlock *b, u64int offset) | 
 | { | 
 | 	int h; | 
 |  | 
 | 	if(b->offset != ~(u64int)0){ | 
 | 		fprint(2, "bad offset in addtohash\n"); | 
 | 		return;	 | 
 | 	} | 
 | 	b->offset = offset; | 
 | 	h = offset % d->nhash; | 
 | 	b->next = d->h[h]; | 
 | 	d->h[h] = b; | 
 | } | 
 |  | 
 | static void | 
 | delfromhash(DiskCache *d, DiskCacheBlock *b) | 
 | { | 
 | 	int h; | 
 | 	DiskCacheBlock **l; | 
 |  | 
 | 	if(b->offset == ~(u64int)0) | 
 | 		return; | 
 |  | 
 | 	h = b->offset % d->nhash; | 
 | 	for(l=&d->h[h]; *l; l=&(*l)->next) | 
 | 		if(*l == b){ | 
 | 			*l = b->next; | 
 | 			b->offset = ~(u64int)0; | 
 | 			return; | 
 | 		} | 
 | 	fprint(2, "delfromhash: didn't find in hash table\n"); | 
 | 	return; | 
 | } | 
 |  | 
 | static void | 
 | putmru(DiskCache *d, DiskCacheBlock *b) | 
 | { | 
 | 	b->lruprev = nil; | 
 | 	b->lrunext = d->lruhead; | 
 | 	d->lruhead = b; | 
 | 	if(b->lrunext == nil) | 
 | 		d->lrutail = b; | 
 | 	else | 
 | 		b->lrunext->lruprev = b; | 
 | } | 
 |  | 
 | static void | 
 | putlru(DiskCache *d, DiskCacheBlock *b) | 
 | { | 
 | 	b->lruprev = d->lrutail; | 
 | 	b->lrunext = nil; | 
 | 	d->lrutail = b; | 
 | 	if(b->lruprev == nil) | 
 | 		d->lruhead = b; | 
 | 	else | 
 | 		b->lruprev->lrunext = b; | 
 | } | 
 |  | 
 | static void | 
 | delfromlru(DiskCache *d, DiskCacheBlock *b) | 
 | { | 
 | 	if(b->lruprev) | 
 | 		b->lruprev->lrunext = b->lrunext; | 
 | 	else | 
 | 		d->lruhead = b->lrunext; | 
 | 	if(b->lrunext) | 
 | 		b->lrunext->lruprev = b->lruprev; | 
 | 	else | 
 | 		d->lrutail = b->lruprev; | 
 | } | 
 |  | 
 | static DiskCacheBlock* | 
 | getlru(DiskCache *d) | 
 | { | 
 | 	DiskCacheBlock *b; | 
 |  | 
 | 	b = d->lrutail; | 
 | 	if(b){ | 
 | 		delfromlru(d, b); | 
 | 		delfromhash(d, b); | 
 | 		blockput(b->subblock); | 
 | 		b->subblock = nil; | 
 | 	} | 
 | 	return b; | 
 | } | 
 |  | 
 | static DiskCacheBlock* | 
 | findblock(DiskCache *d, u64int offset) | 
 | { | 
 | 	int h; | 
 | 	DiskCacheBlock *b; | 
 |  | 
 | 	h = offset % d->nhash; | 
 | 	for(b=d->h[h]; b; b=b->next) | 
 | 		if(b->offset == offset) | 
 | 			return b; | 
 | 	return nil; | 
 | } | 
 |  | 
 | static DiskCacheBlock* | 
 | diskcachereadbig(DiskCache *d, u64int offset) | 
 | { | 
 | 	Block *b; | 
 | 	DiskCacheBlock *dcb; | 
 |  | 
 | 	lock(&d->lk); | 
 | 	dcb = findblock(d, offset); | 
 | 	if(dcb){ | 
 | /*fprint(2, "found %llud in cache %p\n", (uvlong)offset, dcb);*/ | 
 | 		if(dcb->ref++ == 0) | 
 | 			delfromlru(d, dcb); | 
 | 		unlock(&d->lk); | 
 | 		return dcb; | 
 | 	} | 
 |  | 
 | 	dcb = getlru(d); | 
 | 	unlock(&d->lk); | 
 | 	if(dcb == nil){ | 
 | 		fprint(2, "diskcacheread: all blocks in use\n"); | 
 | 		return nil; | 
 | 	} | 
 |  | 
 | 	b = diskread(d->subdisk, d->blocksize, offset); | 
 | 	lock(&d->lk); | 
 | 	if(b == nil){ | 
 | 		putlru(d, dcb); | 
 | 		dcb = nil; | 
 | 	}else{ | 
 | /*fprint(2, "read %llud from disk %p\n", (uvlong)offset, dcb); */ | 
 | 		dcb->subblock = b; | 
 | 		dcb->ref++; | 
 | 		addtohash(d, dcb, offset); | 
 | 	} | 
 | 	unlock(&d->lk); | 
 | 	return dcb; | 
 | } | 
 |  | 
 | static void | 
 | diskcacheblockclose(Block *bb) | 
 | { | 
 | 	DiskCacheBlock *b = bb->priv; | 
 |  | 
 | 	lock(&b->dc->lk); | 
 | 	if(--b->ref == 0) | 
 | 		putmru(b->dc, b); | 
 | 	unlock(&b->dc->lk); | 
 | 	free(bb); | 
 | } | 
 |  | 
 | static Block* | 
 | diskcacheread(Disk *dd, u32int len, u64int offset) | 
 | { | 
 | 	int frag, dlen; | 
 | 	DiskCache *d = (DiskCache*)dd; | 
 | 	DiskCacheBlock *dcb; | 
 | 	Block *b; | 
 |  | 
 | 	if(offset/d->blocksize != (offset+len-1)/d->blocksize){ | 
 | 		fprint(2, "diskBigRead: request for block crossing big block boundary\n"); | 
 | 		return nil; | 
 | 	} | 
 |  | 
 | 	b = mallocz(sizeof(Block), 1); | 
 | 	if(b == nil) | 
 | 		return nil; | 
 |  | 
 | 	frag = offset%d->blocksize; | 
 |  | 
 | 	dcb = diskcachereadbig(d, offset-frag); | 
 | 	if(dcb == nil){ | 
 | 		free(b); | 
 | 		return nil; | 
 | 	} | 
 | 	b->priv = dcb; | 
 | 	b->_close = diskcacheblockclose; | 
 | 	b->data = dcb->subblock->data+frag; | 
 |  | 
 | 	dlen = dcb->subblock->len; | 
 | 	if(frag+len >= dlen){ | 
 | 		if(frag >= dlen){ | 
 | 			blockput(b); | 
 | 			return nil; | 
 | 		} | 
 | 		len = dlen-frag; | 
 | 	} | 
 | 	b->len = len; | 
 | /*fprint(2, "offset %llud at pointer %p %lux\n", (uvlong)offset, b->data, *(ulong*)(b->data+4)); */ | 
 | 	return b; | 
 | } | 
 |  | 
 | /*  | 
 |  * It's okay to remove these from the hash table.  | 
 |  * Either the block is in use by someone or it is on | 
 |  * the lru list.  If it's in use it will get put on the lru | 
 |  * list once the refs go away. | 
 |  */ | 
 | static int | 
 | diskcachesync(Disk *dd) | 
 | { | 
 | 	DiskCache *d = (DiskCache*)dd; | 
 | 	DiskCacheBlock *b, *nextb; | 
 | 	int i; | 
 |  | 
 | 	lock(&d->lk); | 
 | 	for(i=0; i<d->nhash; i++){ | 
 | 		for(b=d->h[i]; b; b=nextb){ | 
 | 			nextb = b->next; | 
 | 			b->next = nil; | 
 | 			b->offset = ~(u64int)0; | 
 | 		} | 
 | 		d->h[i] = nil; | 
 | 	} | 
 | 	unlock(&d->lk); | 
 | 	return disksync(d->subdisk); | 
 | } | 
 |  | 
 | static void | 
 | diskcacheclose(Disk *dd) | 
 | { | 
 | 	DiskCacheBlock *b; | 
 | 	DiskCache *d = (DiskCache*)dd; | 
 |  | 
 | 	diskclose(d->subdisk); | 
 | 	for(b=d->lruhead; b; b=b->lrunext) | 
 | 		blockput(b->subblock); | 
 | 	free(d); | 
 | } | 
 | 		 | 
 | /* needn't be fast */ | 
 | static int | 
 | isprime(int n) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	for(i=2; i*i<=n; i++) | 
 | 		if(n%i == 0) | 
 | 			return 0; | 
 | 	return 1; | 
 | } | 
 |  | 
 | Disk* | 
 | diskcache(Disk *subdisk, uint blocksize, uint ncache) | 
 | { | 
 | 	int nhash, i; | 
 | 	DiskCache *d; | 
 | 	DiskCacheBlock *b; | 
 |  | 
 | 	nhash = ncache; | 
 | 	while(nhash > 1 && !isprime(nhash)) | 
 | 		nhash--; | 
 | 	d = mallocz(sizeof(DiskCache)+ncache*sizeof(DiskCacheBlock)+nhash*sizeof(DiskCacheBlock*), 1); | 
 | 	if(d == nil) | 
 | 		return nil; | 
 |  | 
 | 	b = (DiskCacheBlock*)&d[1]; | 
 | 	d->h = (DiskCacheBlock**)&b[ncache]; | 
 | 	d->nhash = nhash; | 
 | 	d->blocksize = blocksize; | 
 | 	d->subdisk = subdisk; | 
 | 	d->disk._read = diskcacheread; | 
 | 	d->disk._sync = diskcachesync; | 
 | 	d->disk._close = diskcacheclose; | 
 |  | 
 | 	for(i=0; i<ncache; i++){ | 
 | 		b[i].block._close = diskcacheblockclose; | 
 | 		b[i].offset = ~(u64int)0; | 
 | 		b[i].dc = d; | 
 | 		putlru(d, &b[i]); | 
 | 	} | 
 |  | 
 | 	return &d->disk; | 
 | } |