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

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

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

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

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

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

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

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

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

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

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

I64 ATAReadNativeMax(CBlkDev *bd,F64 timeout)
{//Returns zero on err
  I64 res=0;
  Bool okay=TRUE;
  if (bd->type==BDT_ATAPI) {
    if (bd->flags & BDF_EXT_SIZE)
      OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4);
    else
      OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4);
    ATACmd(bd,ATA_DEV_RST);
    if (!ATAWaitNotBUSY(bd,0))
      okay=FALSE;
  } else {
    while (InU8(bd->base0+ATAR0_STAT) & ATAS_BSY) {
      if (bd->flags&BDF_LAST_WAS_WRITE)
        OutU16(bd->base0+ATAR0_DATA,0);
      else
        InU16(bd->base0+ATAR0_DATA);
      Yield;
      if (0<timeout<tS)
        return FALSE;
    }
    if (ATAGetDevId(bd,timeout,TRUE)==BDT_NULL)
      okay=FALSE;
    else
      BEqu(&bd->flags,BDf_EXT_SIZE,Bt(&bd->dev_id_record[86],10));
  }
  if (okay) {
    if (bd->flags & BDF_EXT_SIZE && bd->base1) {
      OutU8(bd->base1+ATAR1_CTRL,0x8);
      OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4);
      ATACmd(bd,ATA_READ_NATIVE_MAX_EXT);
      if (ATAWaitNotBUSY(bd,timeout)) {
        res.u8[0]=InU8(bd->base0+ATAR0_SECT);
        res.u8[1]=InU8(bd->base0+ATAR0_LCYL);
        res.u8[2]=InU8(bd->base0+ATAR0_HCYL);

        OutU8(bd->base1+ATAR1_CTRL,0x80);
        res.u8[3]=InU8(bd->base0+ATAR0_SECT);
        res.u8[4]=InU8(bd->base0+ATAR0_LCYL);
        res.u8[5]=InU8(bd->base0+ATAR0_HCYL);

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

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

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

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

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

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

  if (ATAReadNativeMax(bd,tS+0.1)) {
    ATABlkSel(bd,bd->max_blk,0);
    if (bd->flags&BDF_EXT_SIZE)
      ATACmd(bd,ATA_SET_MAX_EXT);
    else
      ATACmd(bd,ATA_SET_MAX);
    if (ATAWaitNotBUSY(bd,0)) {
      okay=TRUE;
      if (bd->type==BDT_ATAPI) {
        if (ATAPIStartStop(bd,0,TRUE)) {
          if(!ATAPISetMaxSpeed(bd))
            okay=FALSE;
        } else
          okay=FALSE;
      }
    }
  }
  if (unlock) BlkDevUnlock(bd);
  return okay;
}

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

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

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

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

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

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

U0 ATAPIReadBlks(CBlkDev *bd,U8 *buf, I64 blk, I64 cnt)
{
  CDrv *dv=Let2Drv(bd->first_drv_let);
  I64 retry,spc=bd->blk_size>>BLK_SIZE_BITS,n,blk2,
        l2=bd->max_reads<<1+spc<<1;
  U8 *dvd_buf=MAlloc(l2<<BLK_SIZE_BITS);
  if (cnt>0) {
    if (blk<=bd->max_reads)
      blk2=0;
    else
      blk2=FloorU64(blk-bd->max_reads,spc);
    if (blk2+l2>dv->size+dv->drv_offset)
      l2=dv->size+dv->drv_offset-blk2;
    n=(l2+spc-1)/spc;

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

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

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

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

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

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

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

  ATAPISeek(bd,native_blk);

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

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