U0 ATABlkSelect(CBlkDev *bdev,I64 blk,I64 cnt)
{
  if (bdev->type!=BDT_ATAPI && bdev->base1)
    OutU8(bdev->base1+ATAR1_CTRL,0x8);
  if (bdev->flags & BDF_EXT_SIZE) {
    OutU8(bdev->base0+ATAR0_NSECT,cnt.u8[1]);
    OutU8(bdev->base0+ATAR0_SECT,blk.u8[3]);
    OutU8(bdev->base0+ATAR0_LCYL,blk.u8[4]);
    OutU8(bdev->base0+ATAR0_HCYL,blk.u8[5]);
    OutU8(bdev->base0+ATAR0_NSECT,cnt);
    OutU8(bdev->base0+ATAR0_SECT,blk);
    OutU8(bdev->base0+ATAR0_LCYL,blk.u8[1]);
    OutU8(bdev->base0+ATAR0_HCYL,blk.u8[2]);
    OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
  } else {
    OutU8(bdev->base0+ATAR0_NSECT,cnt);
    OutU8(bdev->base0+ATAR0_SECT,blk);
    OutU8(bdev->base0+ATAR0_LCYL,blk.u8[1]);
    OutU8(bdev->base0+ATAR0_HCYL,blk.u8[2]);
    OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4|blk.u8[3]);
  }
}

Bool ATAWaitNotBUSY(CBlkDev *bdev,F64 timeout)
{
  I64 i;
  do {
    for (i=0;i<3;i++)
      if (!(InU8(bdev->base0+ATAR0_STAT)&ATAS_BSY))
	return TRUE;
    Yield;
  } while (!(0<timeout<tS));
  return FALSE;
}

Bool ATAWaitDRQ(CBlkDev *bdev,F64 timeout)
{
  I64 i;
  do {
    for (i=0;i<3;i++)
      if (InU8(bdev->base0+ATAR0_STAT)&ATAS_DRQ)
	return TRUE;
    Yield;
  } while (!(0<timeout<tS));
  return FALSE;
}

Bool ATANop(CBlkDev *bdev,F64 timeout)
{
  if (bdev->flags & BDF_EXT_SIZE)
    OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
  else
    OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4);
  OutU8(bdev->base0+ATAR0_FEAT,0);
  OutU8(bdev->base0+ATAR0_CMD,ATA_NOP);
  return ATAWaitNotBUSY(bdev,timeout);
}

U0 ATACmd(CBlkDev *bdev,U8 cmd)
{
  OutU8(bdev->base0+ATAR0_FEAT,0);
  OutU8(bdev->base0+ATAR0_CMD,cmd);
  bdev->last_time=tS;
  PortNop;
}

Bool ATAGetResult(CBlkDev *bdev,F64 timeout,U8 *buf,I64 cnt,I64 _avail,Bool one_read)
{
  I64 avail,overflow;
  bdev->flags&=~BDF_LAST_WAS_WRITE;
  MemSet(buf,0,cnt);
  while (cnt>0) {
    if (!ATAWaitDRQ(bdev,timeout))
      return FALSE;
    if (_avail)
      avail=_avail;
    else
      avail=InU8(bdev->base0+ATAR0_HCYL)<<8+InU8(bdev->base0+ATAR0_LCYL);
    if (avail) {
      if (avail>cnt) {
	overflow=avail-cnt;
	avail=cnt;
      } else
	overflow=0;
      if (avail&2)
	RepInU16(buf,avail>>1,bdev->base0+ATAR0_DATA);
      else
	RepInU32(buf,avail>>2,bdev->base0+ATAR0_DATA);
      cnt-=avail;
      buf+=avail;
      while (overflow>0) {
	InU16(bdev->base0+ATAR0_DATA);
	overflow-=2;
	if (0<timeout<tS)
	  return FALSE;
      }
      if (one_read)
	break;
    } else
      Yield;
  }
  return ATAWaitNotBUSY(bdev,timeout);
}

Bool ATAPIWritePktWord(CBlkDev *bdev,F64 timeout,...)
{
  I64 i;
  for (i=0;i<argc;i++) {
    if (!ATAWaitDRQ(bdev,timeout))
      return FALSE;
    OutU16(bdev->base0+ATAR0_DATA,EndianU16(argv[i]));
    bdev->last_time=tS;
  }
  return TRUE;
}

Bool ATAPISetMaxSpeed(CBlkDev *bdev)
{
  if (bdev->flags & BDF_EXT_SIZE)
    OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
  else
    OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4);
  OutU8(bdev->base0+ATAR0_LCYL,0);
  OutU8(bdev->base0+ATAR0_HCYL,0);
  ATACmd(bdev,ATA_PACKET);
  ATAPIWritePktWord(bdev,0,0xBB00,0xFFFF,0xFFFF,0,0,0);
  return ATAWaitNotBUSY(bdev,0);
}

Bool ATAPISeek(CBlkDev *bdev,I64 native_blk)
{
  if (bdev->flags & BDF_EXT_SIZE)
    OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
  else
    OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4);
  OutU8(bdev->base0+ATAR0_LCYL,0);
  OutU8(bdev->base0+ATAR0_HCYL,0);
  ATACmd(bdev,ATA_PACKET);
  ATAPIWritePktWord(bdev,0,0x2B00,native_blk>>16,native_blk,0,0,0);
  return ATAWaitNotBUSY(bdev,0);
}

Bool ATAPIStartStop(CBlkDev *bdev,F64 timeout,Bool start)
{
  I64 i;
  if (start)
    i=0x100;
  else
    i=0;
  if (bdev->flags & BDF_EXT_SIZE)
    OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
  else
    OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4);
  ATACmd(bdev,ATA_PACKET);
  //Start/Stop
  if (ATAPIWritePktWord(bdev,timeout,0x1B00,0,i,0,0,0))
    return ATAWaitNotBUSY(bdev,timeout);
  else
    return FALSE;
}

I64 ATAGetDevId(CBlkDev *bdev,F64 timeout,U16 **_id_record)
{
  U16 *id_record;
  if (_id_record) *_id_record=NULL;
  if (bdev->type!=BDT_ATAPI && bdev->base1)
    OutU8(bdev->base1+ATAR1_CTRL,0x8);
  if (bdev->flags & BDF_EXT_SIZE)
    OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
  else
    OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4);
  ATACmd(bdev,ATA_ID_DEV);
  if (!ATAWaitNotBUSY(bdev,timeout))
    return BDT_NULL;
  if (InU8(bdev->base0+ATAR0_STAT)&ATAS_ERR)
    return BDT_ATAPI;
  id_record=ACAlloc(512);
  if (!ATAGetResult(bdev,timeout,id_record,512,512,FALSE)) {
    Free(id_record);
    return BDT_NULL;
  }
  if (_id_record) *_id_record=id_record;
  return BDT_ATA;
}

I64 ATAReadNativeMax(CBlkDev *bdev,F64 timeout)
{//Returns zero on err
  I64 result=0;
  Bool okay=TRUE;

  if (bdev->type==BDT_ATAPI) {
    if (bdev->flags & BDF_EXT_SIZE)
      OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
    else
      OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4);
    ATACmd(bdev,ATA_DEV_RST);
    if (!ATAWaitNotBUSY(bdev,0))
      okay=FALSE;
  } else {
    while (InU8(bdev->base0+ATAR0_STAT) & ATAS_BSY) {
      if (bdev->flags&BDF_LAST_WAS_WRITE)
	OutU16(bdev->base0+ATAR0_DATA,0);
      else
	InU16(bdev->base0+ATAR0_DATA);
      Yield;
      if (0<timeout<tS)
	return FALSE;
    }

    Free(bdev->dev_id_record);
    if (ATAGetDevId(bdev,timeout,&bdev->dev_id_record)==BDT_NULL)
      okay=FALSE;
    else
      BEqu(&bdev->flags,BDf_EXT_SIZE,Bt(&bdev->dev_id_record[86],10));
  }
  if (okay) {
    if (bdev->flags & BDF_EXT_SIZE && bdev->base1) {
      OutU8(bdev->base1+ATAR1_CTRL,0x8);
      OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
      ATACmd(bdev,ATA_READ_NATIVE_MAX_EXT);
      if (ATAWaitNotBUSY(bdev,timeout)) {
	result.u8[0]=InU8(bdev->base0+ATAR0_SECT);
	result.u8[1]=InU8(bdev->base0+ATAR0_LCYL);
	result.u8[2]=InU8(bdev->base0+ATAR0_HCYL);

	OutU8(bdev->base1+ATAR1_CTRL,0x80);
	result.u8[3]=InU8(bdev->base0+ATAR0_SECT);
	result.u8[4]=InU8(bdev->base0+ATAR0_LCYL);
	result.u8[5]=InU8(bdev->base0+ATAR0_HCYL);
 
	if (result>>24==result&0xFFFFFF) {//Kludge to make qemu-kvm work
	  bdev->flags&=~BDF_EXT_SIZE;
	  result&=0xFFFFFF;
	}
      }
    } else {
      if (bdev->type!=BDT_ATAPI && bdev->base1)
	OutU8(bdev->base1+ATAR1_CTRL,0x8);
      OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4);
      ATACmd(bdev,ATA_READ_NATIVE_MAX);
      if (ATAWaitNotBUSY(bdev,timeout)) {
	result.u8[0]=InU8(bdev->base0+ATAR0_SECT);
	result.u8[1]=InU8(bdev->base0+ATAR0_LCYL);
	result.u8[2]=InU8(bdev->base0+ATAR0_HCYL);
	result.u8[3]=InU8(bdev->base0+ATAR0_SELECT) & 0xF;
      }
    }
  }
  return bdev->max_blk=result;
}

I64 ATAPIReadCapacity(CBlkDev *bdev,I64 *_blk_size=NULL)
{//Supposedly this can return a result +/- 75 sects.
//Error might just be for music.
  Bool unlock=BlkDevLock(bdev);
  U32 buf[2];
  if (ATAWaitNotBUSY(bdev,0)) {
    if (bdev->flags & BDF_EXT_SIZE)
      OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
    else
      OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4);
    OutU8(bdev->base0+ATAR0_LCYL,8);
    OutU8(bdev->base0+ATAR0_HCYL,0);
    ATACmd(bdev,ATA_PACKET);
    ATAPIWritePktWord(bdev,0,0x2500,0,0,0,0,0);
    if (!ATAGetResult(bdev,0,buf,8,0,TRUE))
      buf[0]=buf[1]=0;
  } else
    buf[0]=buf[1]=0;

  if (unlock) BlkDevUnlock(bdev);
  if (_blk_size) *_blk_size=EndianU32(buf[1]);
  return EndianU32(buf[0]);
}

CATAPITrack *ATAPIReadTrackInfo(CBlkDev *bdev,I64 blk)
{
  CATAPITrack *result=CAlloc(sizeof(CATAPITrack));
  Bool unlock=BlkDevLock(bdev);
  if (ATAWaitNotBUSY(bdev,0)) {
    if (bdev->flags & BDF_EXT_SIZE)
      OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
    else
      OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4);
    OutU8(bdev->base0+ATAR0_LCYL,sizeof(CATAPITrack)&0xFF);
    OutU8(bdev->base0+ATAR0_HCYL,sizeof(CATAPITrack)>>8);
    ATACmd(bdev,ATA_PACKET);
    ATAPIWritePktWord(bdev,0,0x5200,blk.u16[1],blk.u16[0],
	(sizeof(CATAPITrack)&0xFF00)>>8,(sizeof(CATAPITrack)&0x00FF)<<8,0);
    if (!ATAGetResult(bdev,0,result,sizeof(CATAPITrack),0,TRUE)) {
      Free(result);
      result=NULL;
    }
  } else {
    Free(result);
    result=NULL;
  }
  if (unlock) BlkDevUnlock(bdev);
  return result;
}

Bool ATAInit(CBlkDev *bdev)
{
  Bool unlock=BlkDevLock(bdev),okay=FALSE;

  if (bdev->type==BDT_ATAPI)
    bdev->flags&=~BDF_EXT_SIZE;
  else
    bdev->flags|=BDF_EXT_SIZE;

  if (ATAReadNativeMax(bdev,tS+0.1)) {

    ATABlkSelect(bdev,bdev->max_blk,0);
    if (bdev->flags&BDF_EXT_SIZE)
      ATACmd(bdev,ATA_SET_MAX_EXT);
    else
      ATACmd(bdev,ATA_SET_MAX);
    if (ATAWaitNotBUSY(bdev,0)) {
      okay=TRUE;
      if (bdev->type==BDT_ATAPI) {
	if (ATAPIStartStop(bdev,0,TRUE)) {
	  if(!ATAPISetMaxSpeed(bdev))
	    okay=FALSE;
	} else
	  okay=FALSE;
      }
    }
  }
  if (unlock) BlkDevUnlock(bdev);
  return okay;
}

Bool ATAPIWaitReady(CBlkDev *bdev,F64 timeout)
{
  do {
    if (!ATAWaitNotBUSY(bdev,timeout) ||
	!ATANop(bdev,timeout) ||
	!ATAPIStartStop(bdev,timeout,TRUE))
      return FALSE;
    if (InU8(bdev->base0+ATAR0_STAT) & ATAS_DRDY &&
	!InU8(bdev->base0+ATAR0_FEAT));
      return TRUE;
    ATAInit(bdev);
    Yield;
  } while (!(0<timeout<tS));
  return FALSE;
}

U0 ATAReadBlks(CBlkDev *bdev,U8 *buf, I64 blk, I64 cnt)
{
  I64 retries=3;
  Bool unlock=BlkDevLock(bdev);

retry:
  ATABlkSelect(bdev,blk,cnt);
  if (bdev->flags & BDF_EXT_SIZE)
    ATACmd(bdev,ATA_READ_MULTI_EXT);
  else
    ATACmd(bdev,ATA_READ_MULTI);
  if (!ATAGetResult(bdev,tS+1.0,buf,cnt*bdev->blk_size,BLK_SIZE,FALSE)) {
    if (retries--) {
      ATAWaitNotBUSY(bdev,0);
      goto retry;
    } else
      throw('BlkDev');
  }

  dsk.read_cnt+=(cnt*bdev->blk_size)>>BLK_SIZE_BITS;
  if (unlock) BlkDevUnlock(bdev);
}

I64 ATAProbe(I64 base0,I64 base1,I64 unit)
{
  CBlkDev bdev;
  MemSet(&bdev,0,sizeof(CBlkDev));
  bdev.type=BDT_ATAPI;
  bdev.base0=base0;
  bdev.base1=base1;
  bdev.unit=unit;
  bdev.blk_size=CD_BLK_SIZE;
  return ATAGetDevId(&bdev,tS+0.1,NULL);
}

Bool ATAPIReadBlks2(CBlkDev *bdev,F64 timeout,U8 *buf, I64 native_blk, I64 cnt,Bool lock)
{
  Bool result=FALSE,unlock;
  if (cnt<=0)
    return FALSE;
  if (lock)
    unlock=BlkDevLock(bdev);
  if (ATAPIWaitReady(bdev,timeout)) {
    if (bdev->flags & BDF_EXT_SIZE)
      OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
    else
      OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4);
    OutU8(bdev->base0+ATAR0_LCYL,bdev->blk_size);
    OutU8(bdev->base0+ATAR0_HCYL,bdev->blk_size.u8[1]);
    ATACmd(bdev,ATA_PACKET);
    if (ATAPIWritePktWord(bdev,timeout,0xA800,
	  native_blk.u16[1],native_blk,cnt.u16[1],cnt,0) &&
	  ATAGetResult(bdev,timeout,buf,cnt*bdev->blk_size,0,FALSE)) {
      dsk.read_cnt+=(cnt*bdev->blk_size)>>BLK_SIZE_BITS;
      result=TRUE;
    }
  }
//  ATAPIStartStop(bdev,0,FALSE);
  if (lock && unlock) BlkDevUnlock(bdev);
  return result;
}

U0 ATAPIReadBlks(CBlkDev *bdev,U8 *buf, I64 blk, I64 cnt)
{
  CPrt *p=Drv2Prt(bdev->first_drv_let);
  I64 retry,spc=bdev->blk_size>>BLK_SIZE_BITS,n,blk2,
    l2=bdev->max_reads<<1+spc<<1;
  U8 *cd_buf=MAlloc(l2<<BLK_SIZE_BITS);
  if (cnt>0) {
    if (blk<=bdev->max_reads)
      blk2=0;
    else
      blk2=FloorU64(blk-bdev->max_reads,spc);
    if (blk2+l2>p->size)
      l2=p->size-blk2;
    n=(l2+spc-1)/spc;

    retry=4;
    while (--retry)
      if (ATAPIReadBlks2(bdev,tS+7.0+0.004*n,cd_buf,blk2/spc,n,TRUE))
	//n is 0x800 if max_reads.  Up to 8 additional seconds
	break;
    if (!retry)
      ATAPIReadBlks2(bdev,0,cd_buf,blk2/spc,n,TRUE);
    if (bdev->flags & BDF_READ_CACHE)
      DskCacheAdd(p,cd_buf,blk2,n*spc);
    MemCpy(buf,cd_buf+(blk-blk2)<<BLK_SIZE_BITS,cnt<<BLK_SIZE_BITS);
  }
  Free(cd_buf);
}

Bool ATARBlks(CPrt *p,U8 *buf, I64 blk, I64 cnt)
{
  I64 n;
  CBlkDev *bdev=p->bdev;
  while (cnt>0) {
    n=cnt;
    if (n>bdev->max_reads)
      n=bdev->max_reads;
    if (bdev->type==BDT_ATAPI)
      ATAPIReadBlks(bdev,buf,blk,n);
    else
      ATAReadBlks(bdev,buf,blk,n);
    buf+=n<<BLK_SIZE_BITS;
    blk+=n;
    cnt-=n;
  }
  return TRUE;
}

U0 ATAWriteBlks(CBlkDev *bdev,U8 *buf, I64 blk, I64 cnt)
{//For low level disk access.
//Use WBlks() instead.
  I64 i,U32s_avail,sects_avail,retries=3;
  F64 timeout;
  Bool unlock=BlkDevLock(bdev);
retry:
  ATABlkSelect(bdev,blk,cnt);
  if (bdev->flags&BDF_EXT_SIZE)
    ATACmd(bdev,ATA_WRITE_MULTI_EXT);
  else
    ATACmd(bdev,ATA_WRITE_MULTI);
  bdev->flags|=BDF_LAST_WAS_WRITE;
  while (cnt>0) {
    timeout=tS+1.0;
    while (TRUE) {
      i=InU8(bdev->base0+ATAR0_STAT);
      if (!(i & ATAS_DRDY)||!(i & ATAS_DRQ)) {
	Yield;
      } else
	break;
      if (/* i&ATAS_ERR||*/ tS>timeout) {
	if (retries--) {
	  ATAWaitNotBUSY(bdev,0);
	  goto retry;
	} else
	  throw('BlkDev');
      }
    }
    sects_avail=1;
    U32s_avail=sects_avail<<BLK_SIZE_BITS>>2;
    RepOutU32(buf,U32s_avail,bdev->base0+ATAR0_DATA);
    buf+=U32s_avail<<2;
    cnt-=sects_avail;
    retries=3;
  }
  ATAWaitNotBUSY(bdev,0);
  if (unlock) BlkDevUnlock(bdev);
}

Bool ATAPISync(CBlkDev *bdev)
{
  Bool okay=TRUE;
  if (!ATAWaitNotBUSY(bdev,0))
    okay=FALSE;
  else {
    if (bdev->flags & BDF_EXT_SIZE)
      OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
    else
      OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4);
    OutU8(bdev->base0+ATAR0_LCYL,0);
    OutU8(bdev->base0+ATAR0_HCYL,0);
    ATACmd(bdev,ATA_PACKET);
    ATAPIWritePktWord(bdev,0,0x3500,0,0,0,0,0);
    if (!ATAWaitNotBUSY(bdev,0))
      okay=FALSE;
  }
  return okay;
}

U0 ATAPIClose(CBlkDev *bdev,I64 close_field=0x200,I64 track=0)
{//0x200 CD/DVD part 1
// 0x300    DVD part 2
  if (bdev->flags & BDF_EXT_SIZE)
    OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
  else
    OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4);
  OutU8(bdev->base0+ATAR0_LCYL,0);
  OutU8(bdev->base0+ATAR0_HCYL,0);
  ATACmd(bdev,ATA_PACKET);
  ATAPIWritePktWord(bdev,0,0x5B00,close_field,track,0,0,0);
  ATAWaitNotBUSY(bdev,0);
}

U0 ATAPIWriteBlks(CBlkDev *bdev,U8 *buf, I64 native_blk, I64 cnt)
{
  I64 U32s_avail;
  U8 *buf2;
  ATAWaitNotBUSY(bdev,0);
  ATAPISeek(bdev,native_blk);

  OutU8(bdev->base0+ATAR0_FEAT,0);
  OutU8(bdev->base0+ATAR0_LCYL,bdev->blk_size);
  OutU8(bdev->base0+ATAR0_HCYL,bdev->blk_size.u8[1]);
  if (bdev->flags & BDF_EXT_SIZE)
    OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
  else
    OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4);
  OutU8(bdev->base0+ATAR0_CMD,ATA_PACKET);
  ATAPIWritePktWord(bdev,0,0x0400,native_blk.u16[1],native_blk,cnt.u16[1],cnt,0);
  bdev->flags|=BDF_LAST_WAS_WRITE;
  ATAWaitNotBUSY(bdev,0);

  ATAPISeek(bdev,native_blk);

  if (bdev->flags & BDF_EXT_SIZE)
    OutU8(bdev->base0+ATAR0_SELECT,0xEF|bdev->unit<<4);
  else
    OutU8(bdev->base0+ATAR0_SELECT,0xE0|bdev->unit<<4);
  OutU8(bdev->base0+ATAR0_LCYL,bdev->blk_size);
  OutU8(bdev->base0+ATAR0_HCYL,bdev->blk_size.u8[1]);
  ATACmd(bdev,ATA_PACKET);
  ATAPIWritePktWord(bdev,0,0xAA00,native_blk.u16[1],native_blk,cnt.u16[1],cnt,0);
  buf2=buf+bdev->blk_size*cnt;
  while (buf<buf2) {
    ATAWaitDRQ(bdev,0);
    U32s_avail=(InU8(bdev->base0+ATAR0_HCYL)<<8+InU8(bdev->base0+ATAR0_LCYL))>>2;
    if (buf+U32s_avail<<2>buf2)
      U32s_avail=(buf2-buf)>>2;
    if (U32s_avail) {
      RepOutU32(buf,U32s_avail,bdev->base0+ATAR0_DATA);
      buf+=U32s_avail<<2;
      dsk.write_cnt+=U32s_avail>>(BLK_SIZE_BITS-2);
    }
  }
  ATAWaitNotBUSY(bdev,0);
}

Bool ATAWBlks(CPrt *p,U8 *buf, I64 blk, I64 cnt)
{
  I64 n,spc;
  CBlkDev *bdev=p->bdev;
  Bool unlock;
  spc=bdev->blk_size>>BLK_SIZE_BITS;
  if (bdev->type==BDT_ATAPI) {
    unlock=BlkDevLock(bdev);
    ATAPIWaitReady(bdev,0);
  }
  while (cnt>0) {
    n=cnt;
    if (n>bdev->max_writes)
      n=bdev->max_writes;
    if (bdev->type==BDT_ATAPI)
      ATAPIWriteBlks(bdev,buf,blk/spc,(n+spc-1)/spc);
    else
      ATAWriteBlks(bdev,buf,blk,n);
    buf+=n<<BLK_SIZE_BITS;
    blk+=n;
    cnt-=n;
    dsk.write_cnt+=n;
  }
  if (bdev->type==BDT_ATAPI) {
    ATAPISync(bdev);
//    ATAPIStartStop(bdev,0,FALSE);
    if (unlock) BlkDevUnlock(bdev);
  }
  return TRUE;
}

Bool BootCdProbe(CBlkDev *bdev)
{
  CKernel *img=CAlloc(CD_BLK_SIZE);
  I64 i;
  Bool result=FALSE;
  "Port:%04X,%04XUnit:%02X",bdev->base0,bdev->base1,bdev->unit;
  if (ATAProbe(bdev->base0,bdev->base1,bdev->unit)==BDT_ATAPI) {
    "ATAPI";
    if (ATAPIStartStop(bdev,tS+5.0,TRUE)) {
      "Started";
      for (i=0;i<2;i++) {//Retry
	if (ATAPIReadBlks2(bdev,tS+7.0,img,sys_boot_code.u16[1],1,FALSE)) {
	  if (img->compile_time==sys_compile_time) {
	    "Found";
	    return TRUE;
	  } else
	    "Read";
	} else
	  "NoRead";
      }
    }
  }
  "Nope\n";
  Free(img);
  return result;
}

Bool BootCdProbeAll(CBlkDev *bdev)
{
  I64 d1,d2,i,j,k;

  bdev->base1=0;
  for (k=0;k<256;k++) {
    i=-1;
    while (TRUE) {
      j=PCIClassFind(0x010100+k,++i);
      if (j<0)
	break;
      "Subcode:0x%X Bus:0x%X Dev:0x%X Fun:0x%X\n",k,j.u8[2],j.u8[1],j.u8[0];
      d1=PCIReadU32(j.u8[2],j.u8[1],j.u8[0],0x10);
      d2=PCIReadU32(j.u8[2],j.u8[1],j.u8[0],0x14);
      if (d1&1 && d2&1) {
	if (bdev->base0=d1&-8) {
	  bdev->unit=0;
	  if (BootCdProbe(bdev))
	    return TRUE;
	  bdev->unit=1;
	  if (BootCdProbe(bdev))
	    return TRUE;
	}
      }
      d1=PCIReadU32(j.u8[2],j.u8[1],j.u8[0],0x18);
      d2=PCIReadU32(j.u8[2],j.u8[1],j.u8[0],0x1C);
      if (d1&1 && d2&1) {
	if (bdev->base0=d1&-8) {
	  bdev->unit=0;
	  if (BootCdProbe(bdev))
	    return TRUE;
	  bdev->unit=1;
	  if (BootCdProbe(bdev))
	    return TRUE;
	}
      }
    }
  }

  d1=0x1F0;
  d2=0x3F6;
  if (bdev->base0=d1&-8) {
    bdev->unit=0;
    if (BootCdProbe(bdev))
      return TRUE;
    bdev->unit=1;
    if (BootCdProbe(bdev))
      return TRUE;
  }

  d1=0x170;
  d2=0x376;
  if (bdev->base0=d1&-8) {
    bdev->unit=0;
    if (BootCdProbe(bdev))
      return TRUE;
    bdev->unit=1;
    if (BootCdProbe(bdev))
      return TRUE;
  }
}

U0 ATARepEntry(I64 base0,I64 base1,I64 unit,I64 irq,U8 *msg,CATARep **_root,I64 *num_hints)
{
  I64 type;
  base0&=-8;
  base1&=-4;
  CATARep *tempha;
  if (type=ATAProbe(base0,base1,unit)) {
    *num_hints+=1;
    "\n\n$$PURPLE$$$$BT+X,\"%d\",LM=\"%d\\n\"$$$$FG$$$$LM,4$$",*num_hints,*num_hints;
    if (type==BDT_ATA)
      "$$RED$$ATA";
    else
      "$$RED$$ATAPI";
    "$$LTBLUE$$%s$$FG$$\nBase0:0x%04XBase1:0x%04X\nIrq:0x%02X Unit:%d$$LM,0$$\n",msg,base0,base1,irq,unit;
    if (_root) {
      tempha=CAlloc(sizeof(CATARep));
      tempha->next=*_root;
      *_root=tempha;
      tempha->num=*num_hints;
      tempha->type=type;
      tempha->base0=base0;
      tempha->base1=base1;
      tempha->unit=unit;
    }
  }
}

Bool ATARepExitAllApplications()
{
	"\nWe're going to probe hardware.\n"
	"$$RED$$Exit all other applications.$$FG$$\n"
	"Press '$$PURPLE$$p$$FG$$' to probe or '$$PURPLE$$s$$FG$$' to skip.\n";
  if (ToUpper(GetChar(,FALSE))=='S')
    return TRUE;
  else
    return FALSE;
}

public I64 ATARep(Bool pmt=TRUE,Bool just_ide=FALSE,CATARep **_root=NULL)
{//Report possible ATA devices by probing.  Hard disks and CD/DVDs.
  I64 d1,d2,d3,i,j,k,cnt=0,unlock_flags=0,num_hints=0;
#assert NUM_BLKDEVS<=64
  Bool old_preempt;

  if (_root) *_root=NULL;

  if (pmt && ATARepExitAllApplications)
    return 0;

  old_preempt=Preempt(OFF);
  for (i=0;i<NUM_BLKDEVS;i++)
    if (dsk.blkdevs[i].bd_signature==BD_SIGNATURE_VAL)
      BEqu(&unlock_flags,i,
	    BlkDevLock(&dsk.blkdevs[i]));

  if (!just_ide)
    for (k=0;k<256;k++) {
      i=-1;
      while (TRUE) {
	j=PCIClassFind(0x010100+k,++i);
	if (j<0)
	  break;

	"\nSubcode:0x%X Bus:0x%X Dev:0x%X Fun:0x%X\n",k,j.u8[2],j.u8[1],j.u8[0];
	cnt++;

	d1=PCIReadU32(j.u8[2],j.u8[1],j.u8[0],0x10);
	d2=PCIReadU32(j.u8[2],j.u8[1],j.u8[0],0x14);
	d3=PCIReadU8(j.u8[2],j.u8[1],j.u8[0],0x3C);
	if (d1&1 && d2&1) {
	  ATARepEntry(d1,d2,0,d3,"Primary IDE",_root,&num_hints);
	  ATARepEntry(d1,d2,1,d3,"Primary IDE",_root,&num_hints);
	} else {
	  d1=0x1F0; d2=0x3F6; d3=14;
	  ATARepEntry(d1,d2,0,d3,"Primary IDE",_root,&num_hints);
	  ATARepEntry(d1,d2,1,d3,"Primary IDE",_root,&num_hints);
	}
	d1=PCIReadU32(j.u8[2],j.u8[1],j.u8[0],0x18);
	d2=PCIReadU32(j.u8[2],j.u8[1],j.u8[0],0x1C);
	if (d1&1 && d2&1) {
	  ATARepEntry(d1,d2,0,d3,"Secondary IDE",_root,&num_hints);
	  ATARepEntry(d1,d2,1,d3,"Secondary IDE",_root,&num_hints);
	} else {
	  d1=0x170; d2=0x376; d3=15;
	  ATARepEntry(d1,d2,0,d3,"Secondary IDE",_root,&num_hints);
	  ATARepEntry(d1,d2,1,d3,"Secondary IDE",_root,&num_hints);
	}
      }
    }
  if (!cnt) {
    d1=0x1F0; d2=0x3F6; d3=14;
    ATARepEntry(d1,d2,0,d3,"Primary IDE",_root,&num_hints);
    ATARepEntry(d1,d2,1,d3,"Primary IDE",_root,&num_hints);

    d1=0x170; d2=0x376; d3=15;
    ATARepEntry(d1,d2,0,d3,"Secondary IDE",_root,&num_hints);
    ATARepEntry(d1,d2,1,d3,"Secondary IDE",_root,&num_hints);
  }
  '\n\n';
  for (i=0;i<NUM_BLKDEVS;i++)
    if (Bt(&unlock_flags,i))
      BlkDevUnlock(&dsk.blkdevs[i]);
  Preempt(old_preempt);
  return num_hints;
}

CATARep *ATAIDDrvs(CATARep *root,U8 _sys_acct_drv,CATARep **_ata_drv,CATARep **_atapi_drv)
{//When trying to sort-out main hard drives
//and CD-ROM drives, this is needed.
  CATARep *result=NULL,*tempha=root,*ata_drv=NULL,*atapi_drv=NULL;
  CBlkDev *bdev;
  Bool was_silent=Silent(ON);
  try
    bdev=Drv2BlkDev(_sys_acct_drv);
  catch {
    Fs->catch_except=TRUE;
    bdev=NULL;
  }
  Silent(was_silent);
  while (tempha) {
    if (!result && bdev && bdev->type==tempha->type) {
      if (bdev->type==BDT_ATAPI &&
	  bdev->base0==tempha->base0 && bdev->unit==tempha->unit)
	result=atapi_drv=tempha;
      else if (bdev->type==BDT_ATA && bdev->base0==tempha->base0 &&
	  bdev->base1==tempha->base1 && bdev->unit==tempha->unit)
	result=ata_drv=tempha;
    }
    if (!result || result->type!=tempha->type) {
      if (tempha->type==BDT_ATA) {
	if (!ata_drv || tempha->unit<ata_drv->unit ||
	  tempha->unit==ata_drv->unit && tempha->num<ata_drv->num)
	  ata_drv=tempha;
      } else if (tempha->type==BDT_ATAPI) {
	if (!atapi_drv || tempha->unit<atapi_drv->unit ||
	  tempha->unit==atapi_drv->unit && tempha->num<atapi_drv->num)
	  atapi_drv=tempha;
      }
    }
    tempha=tempha->next;
  }
  if (_ata_drv)   *_ata_drv  =ata_drv;
  if (_atapi_drv) *_atapi_drv=atapi_drv;
  return result;
}

CBlkDev *ATAMount(U8 first_drv_let,I64 type,I64 base0,I64 base1,I64 unit)
{
  CBlkDev *result;
  if (0<=first_drv_let-'A'<NUM_PRTS && (type==BDT_ATA || type==BDT_ATAPI) &&
      0<=unit<=1) {
    result=BlkDevNextFree(first_drv_let,type);
    result->unit=unit;
    result->base0=base0;
    result->base1=base1;
    if (BlkDevAdd(result,FALSE,FALSE))
      return result;
  }
  return NULL;
}

I64 AutoMountIDE()
{//Try to mount hard drive and CD/DVD, automatically. (Kernel.Cfg option).
//It uses 'C' and 'T' as first drive letters or whatever you set
//in config when compiling Kernel.BIN.
  I64 result=0;
  CATARep *root=NULL,*ata_drv=NULL,*atapi_drv=NULL,*tempha;
  ATARep(FALSE,TRUE,&root);
  ATAIDDrvs(root,*sys_acct,&ata_drv,&atapi_drv);
  if (ata_drv && ATAMount(dsk.first_hd_drv_let,BDT_ATA,
	ata_drv->base0,ata_drv->base1,ata_drv->unit))
    result++;
  if (atapi_drv && ATAMount(dsk.first_cd_drv_let,BDT_ATAPI,
      atapi_drv->base0,atapi_drv->base1,atapi_drv->unit))
    result++;
  tempha=root;
  while (tempha) {
    if (tempha!=ata_drv && tempha!=atapi_drv) {
      if (tempha->type==BDT_ATA && ATAMount(dsk.first_hd_drv_let,BDT_ATA,
	  tempha->base0,tempha->base1,tempha->unit))
	result++;
      else if (tempha->type==BDT_ATAPI && ATAMount(dsk.first_cd_drv_let,BDT_ATAPI,
	  tempha->base0,tempha->base1,tempha->unit))
	result++;
    }
    tempha=tempha->next;
  }
  LinkedLstDel(root);
  dsk.auto_mount_ide_cnt=result;
  return result;
}
