| /* |
| * linux/fs/buffer.c |
| * |
| * (C) 1991 Linus Torvalds |
| */ |
| |
| /* |
| * 'buffer.c' implements the buffer-cache functions. Race-conditions have |
| * been avoided by NEVER letting a interrupt change a buffer (except for the |
| * data, of course), but instead letting the caller do it. NOTE! As interrupts |
| * can wake up a caller, some cli-sti sequences are needed to check for |
| * sleep-on-calls. These should be extremely quick, though (I hope). |
| */ |
| |
| /* |
| * NOTE! There is one discordant note here: checking floppies for |
| * disk change. This is where it fits best, I think, as it should |
| * invalidate changed floppy-disk-caches. |
| */ |
| |
| #include <stdarg.h> |
| |
| #include <linux/config.h> |
| #include <linux/sched.h> |
| #include <linux/kernel.h> |
| #include <asm/system.h> |
| #include <asm/io.h> |
| |
| extern int end; |
| static struct buffer_head * start_buffer = (struct buffer_head *) &end; |
| static struct buffer_head * hash_table[NR_HASH]; |
| static struct buffer_head * free_list; |
| static struct task_struct * buffer_wait = NULL; |
| int NR_BUFFERS = 0; |
| |
| static inline void wait_on_buffer(struct buffer_head * bh) |
| { |
| cli(); |
| while (bh->b_lock) |
| sleep_on(&bh->b_wait); |
| sti(); |
| } |
| |
| static void sync_buffers(int dev) |
| { |
| int i; |
| struct buffer_head * bh; |
| |
| bh = free_list; |
| for (i = NR_BUFFERS*2 ; i-- > 0 ; bh = bh->b_next_free) { |
| if (bh->b_lock) |
| continue; |
| if (!bh->b_dirt) |
| continue; |
| ll_rw_block(WRITE,bh); |
| } |
| } |
| |
| int sys_sync(void) |
| { |
| int i; |
| |
| for (i=0 ; i<NR_SUPER ; i++) |
| if (super_block[i].s_dev |
| && super_block[i].s_op |
| && super_block[i].s_op->write_super |
| && super_block[i].s_dirt) |
| super_block[i].s_op->write_super(&super_block[i]); |
| sync_inodes(); /* write out inodes into buffers */ |
| sync_buffers(0); |
| return 0; |
| } |
| |
| int sync_dev(int dev) |
| { |
| struct super_block * sb; |
| |
| if (sb = get_super (dev)) |
| if (sb->s_op && sb->s_op->write_super && sb->s_dirt) |
| sb->s_op->write_super (sb); |
| sync_buffers(dev); |
| sync_inodes(); |
| sync_buffers(dev); |
| return 0; |
| } |
| |
| void inline invalidate_buffers(int dev) |
| { |
| int i; |
| struct buffer_head * bh; |
| |
| bh = start_buffer; |
| for (i=0 ; i<NR_BUFFERS ; i++,bh++) { |
| if (bh->b_dev != dev) |
| continue; |
| wait_on_buffer(bh); |
| if (bh->b_dev == dev) |
| bh->b_uptodate = bh->b_dirt = 0; |
| } |
| } |
| |
| /* |
| * This routine checks whether a floppy has been changed, and |
| * invalidates all buffer-cache-entries in that case. This |
| * is a relatively slow routine, so we have to try to minimize using |
| * it. Thus it is called only upon a 'mount' or 'open'. This |
| * is the best way of combining speed and utility, I think. |
| * People changing diskettes in the middle of an operation deserve |
| * to loose :-) |
| * |
| * NOTE! Although currently this is only for floppies, the idea is |
| * that any additional removable block-device will use this routine, |
| * and that mount/open needn't know that floppies/whatever are |
| * special. |
| */ |
| void check_disk_change(int dev) |
| { |
| int i; |
| struct buffer_head * bh; |
| |
| if (MAJOR(dev) != 2) |
| return; |
| if (!(bh = getblk(dev,0))) |
| return; |
| i = floppy_change(bh); |
| brelse(bh); |
| if (!i) |
| return; |
| for (i=0 ; i<NR_SUPER ; i++) |
| if (super_block[i].s_dev == dev) |
| put_super(super_block[i].s_dev); |
| invalidate_inodes(dev); |
| invalidate_buffers(dev); |
| } |
| |
| #define _hashfn(dev,block) (((unsigned)(dev^block))%NR_HASH) |
| #define hash(dev,block) hash_table[_hashfn(dev,block)] |
| |
| static inline void remove_from_hash_queue(struct buffer_head * bh) |
| { |
| if (bh->b_next) |
| bh->b_next->b_prev = bh->b_prev; |
| if (bh->b_prev) |
| bh->b_prev->b_next = bh->b_next; |
| if (hash(bh->b_dev,bh->b_blocknr) == bh) |
| hash(bh->b_dev,bh->b_blocknr) = bh->b_next; |
| bh->b_next = bh->b_prev = NULL; |
| } |
| |
| static inline void remove_from_free_list(struct buffer_head * bh) |
| { |
| if (!(bh->b_prev_free) || !(bh->b_next_free)) |
| panic("Free block list corrupted"); |
| bh->b_prev_free->b_next_free = bh->b_next_free; |
| bh->b_next_free->b_prev_free = bh->b_prev_free; |
| if (free_list == bh) |
| free_list = bh->b_next_free; |
| bh->b_next_free = bh->b_prev_free = NULL; |
| } |
| |
| static inline void remove_from_queues(struct buffer_head * bh) |
| { |
| remove_from_hash_queue(bh); |
| remove_from_free_list(bh); |
| } |
| |
| static inline void put_first_free(struct buffer_head * bh) |
| { |
| if (!bh || (bh == free_list)) |
| return; |
| remove_from_free_list(bh); |
| /* add to front of free list */ |
| bh->b_next_free = free_list; |
| bh->b_prev_free = free_list->b_prev_free; |
| free_list->b_prev_free->b_next_free = bh; |
| free_list->b_prev_free = bh; |
| free_list = bh; |
| } |
| |
| static inline void put_last_free(struct buffer_head * bh) |
| { |
| if (!bh) |
| return; |
| if (bh == free_list) { |
| free_list = bh->b_next_free; |
| return; |
| } |
| remove_from_free_list(bh); |
| /* add to back of free list */ |
| bh->b_next_free = free_list; |
| bh->b_prev_free = free_list->b_prev_free; |
| free_list->b_prev_free->b_next_free = bh; |
| free_list->b_prev_free = bh; |
| } |
| |
| static inline void insert_into_queues(struct buffer_head * bh) |
| { |
| /* put at end of free list */ |
| bh->b_next_free = free_list; |
| bh->b_prev_free = free_list->b_prev_free; |
| free_list->b_prev_free->b_next_free = bh; |
| free_list->b_prev_free = bh; |
| /* put the buffer in new hash-queue if it has a device */ |
| bh->b_prev = NULL; |
| bh->b_next = NULL; |
| if (!bh->b_dev) |
| return; |
| bh->b_next = hash(bh->b_dev,bh->b_blocknr); |
| hash(bh->b_dev,bh->b_blocknr) = bh; |
| if (bh->b_next) |
| bh->b_next->b_prev = bh; |
| } |
| |
| static struct buffer_head * find_buffer(int dev, int block) |
| { |
| struct buffer_head * tmp; |
| |
| for (tmp = hash(dev,block) ; tmp != NULL ; tmp = tmp->b_next) |
| if (tmp->b_dev==dev && tmp->b_blocknr==block) |
| return tmp; |
| return NULL; |
| } |
| |
| /* |
| * Why like this, I hear you say... The reason is race-conditions. |
| * As we don't lock buffers (unless we are readint them, that is), |
| * something might happen to it while we sleep (ie a read-error |
| * will force it bad). This shouldn't really happen currently, but |
| * the code is ready. |
| */ |
| struct buffer_head * get_hash_table(int dev, int block) |
| { |
| struct buffer_head * bh; |
| |
| for (;;) { |
| if (!(bh=find_buffer(dev,block))) |
| return NULL; |
| bh->b_count++; |
| wait_on_buffer(bh); |
| if (bh->b_dev == dev && bh->b_blocknr == block) { |
| put_last_free(bh); |
| return bh; |
| } |
| bh->b_count--; |
| } |
| } |
| |
| /* |
| * Ok, this is getblk, and it isn't very clear, again to hinder |
| * race-conditions. Most of the code is seldom used, (ie repeating), |
| * so it should be much more efficient than it looks. |
| * |
| * The algoritm is changed: hopefully better, and an elusive bug removed. |
| * |
| * 14.02.92: changed it to sync dirty buffers a bit: better performance |
| * when the filesystem starts to get full of dirty blocks (I hope). |
| */ |
| #define BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock) |
| struct buffer_head * getblk(int dev,int block) |
| { |
| struct buffer_head * bh, * tmp; |
| int buffers; |
| |
| repeat: |
| if (bh = get_hash_table(dev,block)) |
| return bh; |
| buffers = NR_BUFFERS; |
| for (tmp = free_list ; buffers-- > 0 ; tmp = tmp->b_next_free) { |
| if (tmp->b_count) |
| continue; |
| if (!bh || BADNESS(tmp)<BADNESS(bh)) { |
| bh = tmp; |
| if (!BADNESS(tmp)) |
| break; |
| } |
| #if 0 |
| if (tmp->b_dirt) |
| ll_rw_block(WRITEA,tmp); |
| #endif |
| } |
| /* and repeat until we find something good */ |
| if (!bh) { |
| sleep_on(&buffer_wait); |
| goto repeat; |
| } |
| wait_on_buffer(bh); |
| if (bh->b_count) |
| goto repeat; |
| if (bh->b_dirt) { |
| sync_buffers(bh->b_dev); |
| goto repeat; |
| } |
| /* NOTE!! While we slept waiting for this block, somebody else might */ |
| /* already have added "this" block to the cache. check it */ |
| if (find_buffer(dev,block)) |
| goto repeat; |
| /* OK, FINALLY we know that this buffer is the only one of it's kind, */ |
| /* and that it's unused (b_count=0), unlocked (b_lock=0), and clean */ |
| bh->b_count=1; |
| bh->b_dirt=0; |
| bh->b_uptodate=0; |
| remove_from_queues(bh); |
| bh->b_dev=dev; |
| bh->b_blocknr=block; |
| insert_into_queues(bh); |
| return bh; |
| } |
| |
| void brelse(struct buffer_head * buf) |
| { |
| if (!buf) |
| return; |
| wait_on_buffer(buf); |
| if (!(buf->b_count--)) |
| panic("Trying to free free buffer"); |
| wake_up(&buffer_wait); |
| } |
| |
| /* |
| * bread() reads a specified block and returns the buffer that contains |
| * it. It returns NULL if the block was unreadable. |
| */ |
| struct buffer_head * bread(int dev,int block) |
| { |
| struct buffer_head * bh; |
| |
| if (!(bh=getblk(dev,block))) |
| panic("bread: getblk returned NULL\n"); |
| if (bh->b_uptodate) |
| return bh; |
| ll_rw_block(READ,bh); |
| wait_on_buffer(bh); |
| if (bh->b_uptodate) |
| return bh; |
| brelse(bh); |
| return NULL; |
| } |
| |
| #define COPYBLK(from,to) \ |
| __asm__("cld\n\t" \ |
| "rep\n\t" \ |
| "movsl\n\t" \ |
| ::"c" (BLOCK_SIZE/4),"S" (from),"D" (to) \ |
| :"cx","di","si") |
| |
| /* |
| * bread_page reads four buffers into memory at the desired address. It's |
| * a function of its own, as there is some speed to be got by reading them |
| * all at the same time, not waiting for one to be read, and then another |
| * etc. |
| */ |
| void bread_page(unsigned long address,int dev,int b[4]) |
| { |
| struct buffer_head * bh[4]; |
| int i; |
| |
| for (i=0 ; i<4 ; i++) |
| if (b[i]) { |
| if (bh[i] = getblk(dev,b[i])) |
| if (!bh[i]->b_uptodate) |
| ll_rw_block(READ,bh[i]); |
| } else |
| bh[i] = NULL; |
| for (i=0 ; i<4 ; i++,address += BLOCK_SIZE) |
| if (bh[i]) { |
| wait_on_buffer(bh[i]); |
| if (bh[i]->b_uptodate) |
| COPYBLK((unsigned long) bh[i]->b_data,address); |
| brelse(bh[i]); |
| } |
| } |
| |
| /* |
| * Ok, breada can be used as bread, but additionally to mark other |
| * blocks for reading as well. End the argument list with a negative |
| * number. |
| */ |
| struct buffer_head * breada(int dev,int first, ...) |
| { |
| va_list args; |
| struct buffer_head * bh, *tmp; |
| |
| va_start(args,first); |
| if (!(bh=getblk(dev,first))) |
| panic("bread: getblk returned NULL\n"); |
| if (!bh->b_uptodate) |
| ll_rw_block(READ,bh); |
| while ((first=va_arg(args,int))>=0) { |
| tmp=getblk(dev,first); |
| if (tmp) { |
| if (!tmp->b_uptodate) |
| ll_rw_block(READA,tmp); |
| tmp->b_count--; |
| } |
| } |
| va_end(args); |
| wait_on_buffer(bh); |
| if (bh->b_uptodate) |
| return bh; |
| brelse(bh); |
| return (NULL); |
| } |
| |
| void buffer_init(long buffer_end) |
| { |
| struct buffer_head * h = start_buffer; |
| void * b; |
| int i; |
| |
| if (buffer_end == 1<<20) |
| b = (void *) (640*1024); |
| else |
| b = (void *) buffer_end; |
| while ( (b -= BLOCK_SIZE) >= ((void *) (h+1)) ) { |
| if (((unsigned long) (h+1)) > 0xA0000) { |
| printk("buffer-list doesn't fit in low meg - contact Linus\n"); |
| break; |
| } |
| h->b_dev = 0; |
| h->b_dirt = 0; |
| h->b_count = 0; |
| h->b_lock = 0; |
| h->b_uptodate = 0; |
| h->b_wait = NULL; |
| h->b_next = NULL; |
| h->b_prev = NULL; |
| h->b_data = (char *) b; |
| h->b_reqnext = NULL; |
| h->b_prev_free = h-1; |
| h->b_next_free = h+1; |
| h++; |
| NR_BUFFERS++; |
| if (b == (void *) 0x100000) |
| b = (void *) 0xA0000; |
| } |
| h--; |
| free_list = start_buffer; |
| free_list->b_prev_free = h; |
| h->b_next_free = free_list; |
| for (i=0;i<NR_HASH;i++) |
| hash_table[i] = NULL; |
| } |