/*
* 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).
*/
/*
* 'buffer.c'ʵֻٻ湦ܡͨжϹ̸ı仺õ
* ִУ˾Ȼı⣩ע⣡жϿԻһߣ
* ˾Ҫжָcli-stiȴ÷ءҪǳؿ(ϣ)
*/

/*
* 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.
*/
/*


bread
ȡ(getblk)
Ч?
ÿ豸Ͳд
ll_rw_block()
˯ߵȴ״̬


Ч?
ͷŸû
NULL
ػͷָ
* ע⣡һӦǷ
* øóõĵطˣΪҪʹѸ̻ʧЧ
*/

#include <stdarg.h>		// ׼ͷļԺʽбҪ˵-
// (va_list)(va_start, va_arg va_end)
// vsprintfvprintfvfprintf 

#include <linux/config.h>	// ںͷļԺӲͣHD_TYPEѡ
#include <linux/sched.h>	// ȳͷļṹtask_structʼ0 ݣ
// һЩйúͻȡǶʽຯ䡣
#include <linux/kernel.h>	// ںͷļһЩں˳úԭζ塣
#include <asm/system.h>		// ϵͳͷļû޸/жŵȵǶʽꡣ
#include <asm/io.h>		// io ͷļӲ˿/䡣

extern int end;			// ӳld ɵıĩ˵ı[??]
struct buffer_head *start_buffer = (struct buffer_head *) &end;
struct buffer_head *hash_table[NR_HASH];	// NR_HASH = 307 
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 ();			// жϡ
}

//// ϵͳáͬ豸ڴٻݡ
int
sys_sync (void)
{
  int i;
  struct buffer_head *bh;

  sync_inodes ();		/* write out inodes into buffers *//*i ڵдٻ */
// ɨиٻѱ޸ĵĻд󣬽豸ͬ
  bh = start_buffer;
  for (i = 0; i < NR_BUFFERS; i++, bh++)
    {
      wait_on_buffer (bh);	// ȴĻ
      if (bh->b_dirt)
	ll_rw_block (WRITE, bh);	// д豸
    }
  return 0;
}

//// ָ豸иٻ豸ݵͬ
int
sync_dev (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_dirt)
	ll_rw_block (WRITE, bh);
    }
  sync_inodes ();		// i ڵдٻ塣
  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_dirt)
	ll_rw_block (WRITE, bh);
    }
  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.
*/
/*
* ӳһǷѾѾʹٻ
* ӦлЧӳ˵Ҫʹ
* Խִ'mount''open'ʱŵǽٶȺʵϵ
* ÷ڲ̵и̣ᵼݵĶʧǾȡ?
*
* ע⣡Ŀǰӳ̣ԺκοƶʵĿ豸ʹø
* mount/open ǲҪ֪Ƿ̻ʲôʵġ
*/
//// ǷѸʹӦٻЧ
void
check_disk_change (int dev)
{
  int i;

// 豸˳
  if (MAJOR (dev) != 2)
    return;
// ԶӦǷѸû˳
  if (!floppy_change (dev & 0x03))
    return;
// ѾͷŶӦ豸i ڵλͼ߼λͼռĸٻʹ豸
// i ڵݿϢռĸٻЧ
  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);
}

// hash hash ļ궨塣
#define _hashfn(dev,block) (((unsigned)(dev^block))%NR_HASH)
#define hash(dev,block) hash_table[_hashfn(dev,block)]

//// hash кͿлָĻ顣
static inline void
remove_from_queues (struct buffer_head *bh)
{
/* remove from hash-queue */
/* hash Ƴ */
  if (bh->b_next)
    bh->b_next->b_prev = bh->b_prev;
  if (bh->b_prev)
    bh->b_prev->b_next = bh->b_next;
// ûǸöеͷһ飬hash ĶӦָ򱾶еһ
  if (hash (bh->b_dev, bh->b_blocknr) == bh)
    hash (bh->b_dev, bh->b_blocknr) = bh->b_next;
/* remove from free list */
/* ӿлƳ */
  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;
}

//// ָβhash С
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 */
/* ûӦһ豸hash  */
  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;
  bh->b_next->b_prev = bh;
}

//// ڸٻѰҸ豸ָĻ顣
// ҵ򷵻ػָ룬򷵻NULL
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 (;;)
    {
// ڸٻѰҸ豸ָĻûҵ򷵻NULL˳
      if (!(bh = find_buffer (dev, block)))
	return NULL;
// Ըûüȴûѱ
      bh->b_count++;
      wait_on_buffer (bh);
// ھ˯״̬бҪ֤ûȷԣػͷָ롣
      if (bh->b_dev == dev && bh->b_blocknr == block)
	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.
*/
/*
* OKgetblk ú߼ǺͬҲΪҪ
* ⡣д󲿷ִõ(ظ)Ӧ
* ȿȥЧöࡣ
*
* 㷨Ѿ˸ı䣺ϣܸãһĥĴѾȥ
*/
// 궨ͬʱжϻ޸ı־־Ҷ޸ı־ȨҪ־
#define BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock)
//// ȡٻָĻ
// ָĻǷѾڸٻУڣҪڸٻнһӦ
// Ӧͷָ롣
struct buffer_head *
getblk (int dev, int block)
{
  struct buffer_head *tmp, *bh;

repeat:
// hash ָѾڸٻУ򷵻ضӦͷָ룬˳
  if (bh = get_hash_table (dev, block))
    return bh;
// ɨݿѰҿл
// tmp ָĵһлͷ
  tmp = free_list;
  do
    {
// ûʹãü0ɨһ
      if (tmp->b_count)
	continue;
// ͷָbh Ϊգtmp ָͷı־(޸ġ)ȨСbh ͷ־Ȩأ
// bh ָtmp ͷtmp ͷû޸Ҳû־λ
// ˵Ϊָ豸ϵĿȡöӦĸٻ˳ѭ
      if (!bh || BADNESS (tmp) < BADNESS (bh))
	{
	  bh = tmp;
	  if (!BADNESS (tmp))
	    break;
	}
/* and repeat until we find something good *//* ظֱҵʺϵĻ */
    }
  while ((tmp = tmp->b_next_free) != free_list);
// лʹãлͷü>0˯ߣȴпеĻá
  if (!bh)
    {
      sleep_on (&buffer_wait);
      goto repeat;
    }
// ȴûѱĻ
  wait_on_buffer (bh);
// ûֱʹõĻֻظ̡
  if (bh->b_count)
    goto repeat;
// ûѱ޸ģд̣ٴεȴûֱʹ
// Ļֻظ̡
  while (bh->b_dirt)
    {
      sync_dev (bh->b_dev);
      wait_on_buffer (bh);
      if (bh->b_count)
	goto repeat;
    }
/* NOTE!! While we slept waiting for this block, somebody else might */
/* already have added "this" block to the cache. check it */
/* ע⣡Ϊ˵ȴû˯ʱ̿Ѿû */
  *ٻУҪԴ˽м顣*/
// ڸٻhash мָ豸ͿĻǷѾȥǵĻٴظ
// ̡
    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 */
/* OK֪ûָΨһһ飬*/
/* һûбʹ(b_count=0)δ(b_lock=0)Ǹɾģδ޸ĵģ*/
// ռô˻üΪ1λ޸ı־Ч()־
  bh->b_count = 1;
  bh->b_dirt = 0;
  bh->b_uptodate = 0;
// hash кͿпƳûͷøûָ豸ϵָ顣
  remove_from_queues (bh);
  bh->b_dev = dev;
  bh->b_blocknr = block;
// Ȼݴµ豸źͿ²hash λôշػͷָ롣
  insert_into_queues (bh);
  return bh;
}

//// ͷָĻ
// ȴûüݼ1ѵȴлĽ̡
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.
*/
/*
* 豸϶ȡָݿ鲢غݵĻָĿ鲻
* 򷵻NULL
*/
//// ָ豸϶ȡָݿ顣
struct buffer_head *
bread (int dev, int block)
{
  struct buffer_head *bh;

// ڸٻһ黺ֵNULL ָ룬ʾں˳
  if (!(bh = getblk (dev, block)))
    panic ("bread: getblk returned NULL\n");
// ûеЧģѸµģֱʹã򷵻ء
  if (bh->b_uptodate)
    return bh;
// ll_rw_block()豸󡣲ȴ
  ll_rw_block (READ, bh);
  wait_on_buffer (bh);
// ûѸ£򷵻ػͷָ룬˳
  if (bh->b_uptodate)
    return bh;
// 豸ʧܣͷŸûNULL ָ룬˳
  brelse (bh);
  return NULL;
}

//// ڴ顣
// from ַһݵto λá
#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.
*/
/*
* bread_page һζĸݶڴָĵַһĺ
* ΪͬʱȡĿԻٶϵĺôõŶһ飬ٶһˡ
*/
//// 豸һҳ棨4 飩ݵڴָĵַ
void
bread_page (unsigned long address, int dev, int b[4])
{
  struct buffer_head *bh[4];
  int i;

// ѭִ4 Σһҳݡ
  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;
// 4 黺ϵ˳Ƶַָ
  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.
*/
/*
* OKbreada bread һʹãԤһЩ顣úб
* ҪʹһбĽ
*/
//// ָ豸ȡָһЩ顣
// ɹʱص1 Ļͷָ룬򷵻NULL
struct buffer_head *
breada (int dev, int first, ...)
{
  va_list args;
  struct buffer_head *bh, *tmp;

// ȡɱе1 ţ
  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, bh);
	  tmp->b_count--;
	}
    }
// ɱвϡȴ1 ѱ
  va_end (args);
  wait_on_buffer (bh);
// Ч򷵻ػͷָ룬˳ͷŸûNULL˳
  if (bh->b_uptodate)
    return bh;
  brelse (bh);
  return (NULL);
}

//// ʼ
// buffer_end ָĻڴĩˡϵͳ16MB ڴ棬򻺳ĩΪ4MB
// ϵͳ8MB ڴ棬ĩΪ2MB
void
buffer_init (long buffer_end)
{
  struct buffer_head *h = start_buffer;
  void *b;
  int i;

// ߶˵1Mbڴ640KB-1MB ʾڴBIOS ռãʵʿûڴ
// ߶Ӧ640KBڴ߶һ1MB
  if (buffer_end == 1 << 20)
    b = (void *) (640 * 1024);
  else
    b = (void *) buffer_end;
// δڳʼлȡϵͳлĿ
// ĹǴӻ߶˿ʼ1K СĻ飬ͬʱڻͶ˽û
// Ľṹbuffer_headЩbuffer_head ˫
// h ָ򻺳ͷṹָ룬h+1 ָڴַһͷַҲ˵ָh
// ͷĩ⡣Ϊ˱֤㹻ȵڴ洢һͷṹҪb ָڴ
// ַ >= h ͷĩˣҲҪ>=h+1
  while ((b -= BLOCK_SIZE) >= ((void *) (h + 1)))
    {
      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;		// ָͬhash ֵһͷ
      h->b_prev = NULL;		// ָͬhash ֵǰһͷ
      h->b_data = (char *) b;	// ָӦݿ飨1024 ֽڣ
      h->b_prev_free = h - 1;	// ָǰһ
      h->b_next_free = h + 1;	// ָһ
      h++;			// h ָһ»ͷλá
      NR_BUFFERS++;		// ۼӡ
      if (b == (void *) 0x100000)	// ַb ݼ1MB384KB
	b = (void *) 0xA0000;	// b ַָ0xA0000(640KB)
    }
  h--;				// h ָһЧͷ
  free_list = start_buffer;	// ÿͷָͷһͷ
  free_list->b_prev_free = h;	// ͷb_prev_free ָǰһһ
  h->b_next_free = free_list;	// h һָָһγһ
// ʼhash ϣɢбñеָΪNULL
  for (i = 0; i < NR_HASH; i++)
    hash_table[i] = NULL;
}
