/*
* linux/kernel/hd.c
*
* (C) 1991 Linus Torvalds
*/

/*
* This is the low-level hd interrupt support. It traverses the
* request-list, using interrupts to jump between functions. As
* all the functions are called within interrupts, we may not
* sleep. Special care is recommended.
*
* modified by Drew Eckhardt to check nr of hd's from the CMOS.
*/
/*
* ǵײӲжϸҪɨбʹжں֮ת
* еĺжõģЩ˯ߡرע⡣
* Drew Eckhardt ޸ģCMOS ϢӲ
*/

#include <linux/config.h>	// ںͷļԺӲͣHD_TYPEѡ
#include <linux/sched.h>	// ȳͷļṹtask_structʼ0 ݣ
// һЩйúͻȡǶʽຯ䡣
#include <linux/fs.h>		// ļϵͳͷļļṹfile,buffer_head,m_inode ȣ
#include <linux/kernel.h>	// ںͷļһЩں˳úԭζ塣
#include <linux/hdreg.h>	// Ӳ̲ͷļӲ̼Ĵ˿ڣ״̬룬Ϣ
#include <asm/system.h>		// ϵͳͷļû޸/жŵȵǶʽꡣ
#include <asm/io.h>		// io ͷļӲ˿/䡣
#include <asm/segment.h>	// βͷļйضμĴǶʽຯ

#define MAJOR_NR 3		// Ӳ豸3
#include "blk.h"		// 豸ͷļݽṹ豸ݽṹͺ꺯Ϣ

#define CMOS_READ(addr) ({ \	// CMOS 꺯
outb_p (0x80 | addr, 0x70);
inb_p (0x71);
}

)
/* Max read/write errors/sector */
#define MAX_ERRORS 7		// /дһʱ
#define MAX_HD 2		// ϵͳֵ֧Ӳ
     static void recal_intr (void);	// ӲжϳڸλʱõУ(287 )

     static int recalibrate = 1;	// У־
     static int reset = 1;	// λ־

/*
* This struct defines the HD's and their types.
*/
/* ṹӲ̲ */
// ֶηֱǴͷÿŵдǰԤšͷ½šֽڡ
     struct hd_i_struct
     {
       int head, sect, cyl, wpcom, lzone, ctl;
     };

#ifdef HD_TYPE			// Ѿinclude/linux/config.h жHD_TYPE
struct hd_i_struct hd_info[] =
{
HD_TYPE};			// ȡõĲΪhd_info[]ݡ

#define NR_HD ((sizeof (hd_info))/(sizeof (struct hd_i_struct)))	// Ӳ
#else // 򣬶Ϊ0 ֵ
struct hd_i_struct hd_info[] =
{
  {
  0, 0, 0, 0, 0, 0}
  ,
  {
  0, 0, 0, 0, 0, 0}
};
static int NR_HD = 0;
#endif

// Ӳ̷ṹÿʼš
// 5 ıhd[0]hd[5]ȣӲеĲ
static struct hd_struct
{
  long start_sect;
  long nr_sects;
}

hd[5 * MAX_HD] =
{
  {
  0, 0}
,};

// ˿portnr ֣buf С
#define port_read(port,buf,nr) \
__asm__( "cld;rep;insw":: "d" (port), "D" (buf), "c" (nr): "cx", "di")

// д˿portдnr ֣buf ȡݡ
#define port_write(port,buf,nr) \
__asm__( "cld;rep;outsw":: "d" (port), "S" (buf), "c" (nr): "cx", "si")

extern void hd_interrupt (void);
extern void rd_load (void);

/* This may be used only once, enforced by 'static int callable' */
/* úֻڳʼʱһΡþ̬callable Ϊɵñ־*/
// úĲɳʼinit/main.c init ӳΪָ0x90080 ˴setup.s
// BIOS ȡõ2 Ӳ̵Ļ(32 ֽ)Ӳ̲Ϣμб˵
// ҪǶȡCMOS Ӳ̲ϢӲ̷ṹhdRAM ̺
// ļϵͳ
int sys_setup (void *BIOS)
{
  static int callable = 1;
  int i, drive;
  unsigned char cmos_disks;
  struct partition *p;
  struct buffer_head *bh;

// ʼʱcallable=1иúʱΪ0ʹִֻһΡ
  if (!callable)
      return -1;
    callable = 0;
// ûconfig.h жӲ̲ʹ0x90080 롣
#ifndef HD_TYPE
  for (drive = 0; drive < 2; drive++)
    {
      hd_info[drive].cyl = *(unsigned short *) BIOS;	// 
      hd_info[drive].head = *(unsigned char *) (2 + BIOS);	// ͷ
      hd_info[drive].wpcom = *(unsigned short *) (5 + BIOS);	// дǰԤš
      hd_info[drive].ctl = *(unsigned char *) (8 + BIOS);	// ֽڡ
      hd_info[drive].lzone = *(unsigned short *) (12 + BIOS);	// ͷ½š
      hd_info[drive].sect = *(unsigned char *) (14 + BIOS);	// ÿŵ
      BIOS += 16;		// ÿӲ̵Ĳ16 ֽڣBIOS ָһ
    }
// setup.s ȡBIOS еӲ̲Ϣʱֻ1 Ӳ̣ͻὫӦ2 Ӳ̵
// 16 ֽȫ㡣ֻҪжϵ2 ӲǷΪ0 Ϳ֪ûе2 Ӳˡ
  if (hd_info[1].cyl)
      NR_HD = 2;		// ӲΪ2
  else
    NR_HD = 1;
#endif
// ÿӲ̵ʼźбi*5 μй˵
  for (i = 0; i < NR_HD; i++)
    {
      hd[i * 5].start_sect = 0;	// Ӳʼš
      hd[i * 5].nr_sects = hd_info[i].head * hd_info[i].sect * hd_info[i].cyl;	// Ӳ
    }

/*
We querry CMOS about hard disks : it could be that
we have a SCSI/ESDI/etc controller that is BIOS
compatable with ST-506, and thus showing up in our
BIOS table, but not register compatable, and therefore
not present in CMOS.

Furthurmore, we will assume that our ST-506 drives
<if any> are the primary drives in the system, and
the ones reflected as drive 1 or 2.

The first drive is stored in the high nibble of CMOS
byte 0x12, the second in the low nibble. This will be
either a 4 bit drive type or 0xf indicating use byte 0x19
for an 8 bit type, drive 1, 0x1a for drive 2 in CMOS.

Needless to say, a non-zero value means we have
an AT controller hard disk for that drive.


*/
/*
* ǶCMOS йӲ̵ϢЩɣܻһSCSI/ESDI/ȵ
* ST-506 ʽBIOS ݵģǵBIOS Уȴֲ
* ǼĴݵģЩCMOS ֲڡ
* ⣬ǼST-506 еĻϵͳеĻҲ1 2
* ֵ
* 1 CMOS ֽ0x12 ĸֽ߰У2 ڵͰֽС4 λֽ
* ϢͣҲܽ0xf0xf ʾʹCMOS 0x19 ֽΪ1 8 λ
* ֽڣʹCMOS 0x1A ֽΪ2 ֽڡ
* ֮һֵζһAT Ӳ̼ݵ
*/

// ԭӲ̵ǷAT ݵġйCMOS Ϣμ4.2.3.1 ڡ
  if ((cmos_disks = CMOS_READ (0x12)) & 0xf0)
    if (cmos_disks & 0x0f)
      NR_HD = 2;
    else
      NR_HD = 1;
  else
    NR_HD = 0;
// NR_HD=0Ӳ̶AT ݵģӲݽṹ㡣
// NR_HD=1򽫵2 Ӳ̵Ĳ㡣
  for (i = NR_HD; i < 2; i++)
    {
      hd[i * 5].start_sect = 0;
      hd[i * 5].nr_sects = 0;
    }
// ȡÿһӲϵ1 ݣ1 ãȡеķϢ
// úbread()Ӳ̵1 (fs/buffer.c,267)е0x300 Ӳ̵豸
// (μб˵)ȻӲͷ1 λ0x1fe ֽǷΪ'55AA'ж
// λ0x1BE ʼķǷЧ󽫷ϢӲ̷ݽṹhd С
  for (drive = 0; drive < NR_HD; drive++)
    {
      if (!(bh = bread (0x300 + drive * 5, 0)))
	{			// 0x300, 0x305 ߼豸š
	  printk ("Unable to read partition table of drive %d\n\r", drive);
	  panic ("");
	}
      if (bh->b_data[510] != 0x55 || (unsigned char) bh->b_data[511] != 0xAA)
	{			// жӲϢЧ־'55AA'
	  printk ("Bad partition table on drive %d\n\r", drive);
	  panic ("");
	}
      p = 0x1BE + (void *) bh->b_data;	// λӲ̵1 0x1BE 
      for (i = 1; i < 5; i++, p++)
	{
	  hd[i + 5 * drive].start_sect = p->start_sect;
	  hd[i + 5 * drive].nr_sects = p->nr_sects;
	}
      brelse (bh);		// ͷΪӲ̿ڴ滺ҳ
    }
  if (NR_HD)			// Ӳ̴ڲѶӡϢ
    printk ("Partition table%s ok.\n\r", (NR_HD > 1) ? "s" : "");
  rd_load ();			// أRAMDISK(kernel/blk_drv/ramdisk.c,71)
  mount_root ();		// װļϵͳ(fs/super.c,242)
  return (0);
}

//// жϲѭȴ
// Ӳ̿״̬Ĵ˿HD_STATUS(0x1f7)ѭλͿæλ
static int controller_ready (void)
{
  int retries = 10000;

  while (--retries && (inb_p (HD_STATUS) & 0xc0) != 0x40);
    return (retries);		// صȴѭĴ
}

//// Ӳִ״̬(win_ʾ˹Ӳ̵д)
// ȡ״̬Ĵеִн״̬0 ʾ1 ִ
// ٶĴHD_ERROR(0x1f1)
static int win_result (void)
{
  int i = inb_p (HD_STATUS);	// ȡ״̬Ϣ

  if ((i & (BUSY_STAT | READY_STAT | WRERR_STAT | SEEK_STAT | ERR_STAT))
      == (READY_STAT | SEEK_STAT))
      return (0);		/* ok */
  if (i & 1)
      i = inb (HD_ERROR);	// ERR_STAT λȡĴ
    return (1);
}

//// Ӳ̿飨μб˵
// òdrive - Ӳ̺(0-1) nsect - д
// sect - ʼ head - ͷţ
// cyl - ţ cmd - 룻
// *intr_addr() - ӲжϴнõC 
static void hd_out (unsigned int drive, unsigned int nsect, unsigned int sect,
		    unsigned int head, unsigned int cyl, unsigned int cmd,
		    void (*intr_addr) (void))
{
  register int port asm ("dx");	// port ӦĴdx

  if (drive > 1 || head > 15)	// (0,1)>1 ͷ>15֧֡
    panic ("Trying to write bad sector");
  if (!controller_ready ())	// ȴһʱδ
    panic ("HD controller not ready");
  do_hd = intr_addr;		// do_hd ָ뽫Ӳжϳбá
  outb_p (hd_info[drive].ctl, HD_CMD);	// ƼĴ(0x3f6)ֽڡ
  port = HD_DATA;		// dx ΪݼĴ˿(0x1f0)
  outb_p (hd_info[drive].wpcom >> 2, ++port);	// дԤ(4)
  outb_p (nsect, ++port);	// /д
  outb_p (sect, ++port);	// ʼ
  outb_p (cyl, ++port);		// ŵ8 λ
  outb_p (cyl >> 8, ++port);	// Ÿ8 λ
  outb_p (0xA0 | (drive << 4) | head, ++port);	// +ͷš
  outb (cmd, ++port);		// Ӳ̿
}

//// ȴӲ̾Ҳѭȴ״̬æ־λλоѰ־
// λɹ0һʱΪæ򷵻1
static int drive_busy (void)
{
  unsigned int i;

  for (i = 0; i < 10000; i++)	// ѭȴ־λλ
    if (READY_STAT == (inb_p (HD_STATUS) & (BUSY_STAT | READY_STAT)))
        break;
    i = inb (HD_STATUS);	// ȡ״ֽ̬ڡ
    i &= BUSY_STAT | READY_STAT | SEEK_STAT;	// æλλѰλ
  if (i == READY_STAT | SEEK_STAT)	// оѰ־򷵻0
      return (0);
    printk ("HD controller times out\n\r");	// ȴʱʾϢ1
    return (1);
}

//// ϸλУӲ̿
static void reset_controller (void)
{
  int i;

    outb (4, HD_CMD);		// ƼĴ˿ڷͿֽ(4-λ)
  for (i = 0; i < 100; i++)
      nop ();			// ȴһʱ䣨ѭղ
    outb (hd_info[0].ctl & 0x0f, HD_CMD);	// ٷĿֽ(ֹԡض)
  if (drive_busy ())		// ȴӲ̾ʱʾϢ
      printk ("HD-controller still busy\n\r");
  if ((i = inb (HD_ERROR)) != 1)	// ȡĴ1޴
      printk ("HD-controller reset failed: %02x\n\r", i);
}

//// λӲnrȸλУӲ̿ȻӲ̿
// recal_intr()ӲжϴеõУ
static void reset_hd (int nr)
{
  reset_controller ();
  hd_out (nr, hd_info[nr].sect, hd_info[nr].sect, hd_info[nr].head - 1,
	  hd_info[nr].cyl, WIN_SPECIFY, &recal_intr);
}

//// Ӳжϵú
// ӲжʱӲжϴеõĬC ڱúָΪʱ
// øúμ(kernel/system_call.s,241 )
void unexpected_hd_interrupt (void)
{
  printk ("Unexpected HD interrupt\n\r");
}

//// дӲʧܴú
static void bad_rw_intr (void)
{
  if (++CURRENT->errors >= MAX_ERRORS)	// ʱĳڻ7 ʱ
    end_request (0);		// 󲢻ѵȴḶ̌
// Ӧ±־λûи£
  if (CURRENT->errors > MAX_ERRORS / 2)	// һʱĳѾ3 Σ
    reset = 1;			// ҪִиλӲ̿
}

//// жϵúִӲжϴбá
static void read_intr (void)
{
  if (win_result ())
    {				// æдִд
      bad_rw_intr ();		// ждӲʧܴ
      do_hd_request ();		// ȻٴӲӦ(λ)
      return;
    }
  port_read (HD_DATA, CURRENT->buffer, 256);	// ݴݼĴڶṹ
  CURRENT->errors = 0;		// 
  CURRENT->buffer += 512;	// ָ룬ָµĿ
  CURRENT->sector++;		// ʼż1
  if (--CURRENT->nr_sectors)
    {				// ûж꣬
      do_hd = &read_intr;	// ٴӲ̵C ָΪread_intr()
      return;			// ΪӲжϴÿεdo_hd ʱ
    }				// Ὣúָÿաμsystem_call.s
  end_request (1);		// ȫѾ꣬ˣ
  do_hd_request ();		// ִӲ
}

//// джϵúӲжϴбá
// дִк󣬻ӲжźţִӲжϴ򣬴ʱӲжϴеõ
// C ָdo_hd()Ѿָwrite_intr()˻дɣִиú
static void write_intr (void)
{
  if (win_result ())
    {				// Ӳ̿شϢ
      bad_rw_intr ();		// ȽӲ̶дʧܴ
      do_hd_request ();		// ȻٴӲӦ(λ)
      return;			// Ȼ󷵻أҲ˳˴˴Ӳжϣ
    }
  if (--CURRENT->nr_sectors)
    {				// д1Ҫд
      CURRENT->sector++;	// ǰʼ+1
      CURRENT->buffer += 512;	// 󻺳ָ룬
      do_hd = &write_intr;	// ӲжϳúָΪwrite_intr()
      port_write (HD_DATA, CURRENT->buffer, 256);	// ݼĴ˿д256 ֽڡ
      return;			// صȴӲٴджϴ
    }
  end_request (1);		// ȫѾд꣬ˣ
  do_hd_request ();		// ִӲ
}

//// ӲУλжϵúӲжϴбá
// Ӳ̿شϢȽӲ̶дʧܴȻӲӦ(λ)
static void recal_intr (void)
{
  if (win_result ())
    bad_rw_intr ();
  do_hd_request ();
}

// ִӲ̶д
void do_hd_request (void)
{
  int i, r;
  unsigned int block, dev;
  unsigned int sec, head, cyl;
  unsigned int nsect;

    INIT_REQUEST;		// ĺϷ(μkernel/blk_drv/blk.h,127)
// ȡ豸е豸(бӲ豸ŵ˵)豸żӲϵķš
    dev = MINOR (CURRENT->dev);	// CURRENT Ϊ(blk_dev[MAJOR_NR].current_request)
    block = CURRENT->sector;	// ʼ
// 豸Ųڻʼڸ÷-2󣬲תrepeat 
// INIT_REQUEST ʼΪһҪд2 512*2 ֽڣ
// ܴڷڶš
  if (dev >= 5 * NR_HD || block + 2 > hd[dev].nr_sects)
    {
      end_request (0);
      goto repeat;		// ñblk.h 档
    }
  block += hd[dev].start_sect;	// ĿӦӲϵľš
  dev /= 5;			// ʱdev Ӳ̺ţ0 1
// ǶӲϢṹиʼźÿŵڴŵе
// (sec)(cyl)ʹͷ(head)
__asm__ ("divl %4": "=a" (block), "=d" (sec):"" (block), "1" (0),
	   "r" (hd_info[dev].
		sect));
__asm__ ("divl %4": "=a" (cyl), "=d" (head):"" (block), "1" (0),
	   "r" (hd_info[dev].
		head));
  sec++;
  nsect = CURRENT->nr_sectors;	// /д
// reset 1ִиλλӲ̺ͿҪУ־ء
  if (reset)
    {
      reset = 0;
      recalibrate = 1;
      reset_hd (CURRENT_DEV);
      return;
    }
// У־(recalibrate)λȸλñ־ȻӲ̿У
  if (recalibrate)
    {
      recalibrate = 0;
      hd_out (dev, hd_info[CURRENT_DEV].sect, 0, 0, 0,
	      WIN_RESTORE, &recal_intr);
      return;
    }
// ǰддѭȡ״̬ĴϢж־
// DRQ_STAT ǷλDRQ_STAT Ӳ״̬Ĵλinclude/linux/hdreg.h27
  if (CURRENT->cmd == WRITE)
    {
      hd_out (dev, nsect, sec, head, cyl, WIN_WRITE, &write_intr);
      for (i = 0; i < 3000 && !(r = inb_p (HD_STATUS) & DRQ_STAT); i++)
/* nothing */ ;
// λλ˳ѭȵѭҲûλ˴дӲ̲ʧܣȥ
// һӲ󡣷Ӳ̿ݼĴ˿HD_DATA д1 ݡ
      if (!r)
	{
	  bad_rw_intr ();
	  goto repeat;		// ñblk.h 棬Ҳ301 С
	}
      port_write (HD_DATA, CURRENT->buffer, 256);
// ǰǶӲӲ̿Ͷ
    }
  else if (CURRENT->cmd == READ)
    {
      hd_out (dev, nsect, sec, head, cyl, WIN_READ, &read_intr);
    }
  else
    panic ("unknown hd-command");
}

// Ӳϵͳʼ
void hd_init (void)
{
  blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;	// do_hd_request()
  set_intr_gate (0x2E, &hd_interrupt);	// Ӳж int 0x2E(46)
// hd_interrupt (kernel/system_call.s,221)
  outb_p (inb_p (0x21) & 0xfb, 0x21);	// λ8259A int2 λƬ
// жźš
  outb (inb_p (0xA1) & 0xbf, 0xA1);	// λӲ̵жλڴƬϣ
// Ӳ̿жźš
}
