分析IDE硬盘驱动器读写过程

发表于:2007-07-04来源:作者:点击数: 标签:
作者:opera Linux内核在缺省配置下最多支持10个IDE接口,IDE接口用ide_hwif_t结构来描述,每个IDE接口具有一对主-从驱动器接口,它们用ide_drive_t结构来描述,每个驱动器接口可接不同种类的IDE设备,如IDE硬盘,光驱等,它们用ide_driver_t结构来描述. 每个驱动器

  作者:opera
  Linux内核在缺省配置下最多支持10个IDE接口,IDE接口用ide_hwif_t结构来描述,每个IDE接口具有一对主-从驱动器接口,它们用ide_drive_t结构来描述,每个驱动器接口可接不同种类的IDE设备,如IDE硬盘,光驱等,它们用ide_driver_t结构来描述.
  每个驱动器接口包含一个命令请求队列,用request_queue_t结构来描述,具体的请求用request结构来描述.
  多个IDE驱动器可以共享一个中断,共享同一中断的驱动器形成一个组,用ide_hwgroup_t结构来描述.ide_intr()是所有的ide驱动器所共用的硬件中断入口,对之对应的ide_hwgroup_t指针将作为dev_id传递给ide_intr.
  每次在读写某个驱动器之前,需要用ide_set_handler()来设置ide_intr将要调用的中断函数指针.中断产生以后,该函数指针被自动清除.
  do_rw_disk(drive,rq,block) 从逻辑扇区号block开始向IDE硬盘驱动器drive写入rq所描述的内容.
  以下是硬盘PIO传输模式的有关代码.
  
  ; drivers/ide/ide-disk.c
  static ide_startstop_t do_rw_disk (ide_drive_t *drive, struct request *rq, unsigned long block)
  {
  if (IDE_CONTROL_REG)
  OUT_BYTE(drive->ctl,IDE_CONTROL_REG);
  OUT_BYTE(rq->nr_sectors,IDE_NSECTOR_REG);
  if (drive->select.b.lba) { 如果是逻辑块寻址模式
  OUT_BYTE(block,IDE_SECTOR_REG);
  OUT_BYTE(block>>=8,IDE_LCYL_REG);
  OUT_BYTE(block>>=8,IDE_HCYL_REG);
  OUT_BYTE(((block>>&0x0f)|drive->select.all,IDE_SELECT_REG);
  } else {
  unsigned int sect,head,cyl,track;
  track = block / drive->sect;
  sect = block % drive->sect + 1;
  OUT_BYTE(sect,IDE_SECTOR_REG);
  head = track % drive->head;
  cyl = track / drive->head;
  OUT_BYTE(cyl,IDE_LCYL_REG);
  OUT_BYTE(cyl>>8,IDE_HCYL_REG);
  OUT_BYTE(head|drive->select.all,IDE_SELECT_REG);
  }
  if (rq->cmd == READ) {{
  ide_set_handler(drive, &read_intr, WAIT_CMD, NULL); WAIT_CMD为10秒超时
  OUT_BYTE(drive->mult_count ? WIN_MULTREAD : WIN_READ, IDE_COMMAND_REG);
  return ide_started;
  }
  if (rq->cmd == WRITE) {
  ide_startstop_t startstop;
  OUT_BYTE(drive->mult_count ? WIN_MULTWRITE : WIN_WRITE, IDE_COMMAND_REG);
  if (ide_wait_stat(&startstop, drive, DATA_READY, drive->bad_wstat, WAIT_DRQ)) {
  printk(KERN_ERR "%s: no DRQ after issuing %s\n", drive->name,
  drive->mult_count ? "MULTWRITE" : "WRITE");
  return startstop;
  }
  if (!drive->unmask)
  __cli(); /* local CPU only */
  if (drive->mult_count) { 如果允许多扇区传送
  ide_hwgroup_t *hwgroup = HWGROUP(drive);
  /*
  * Ugh.. this part looks ugly because we MUST set up
  * the interrupt handler before outputting the first block
  * of data to be written. If we hit an error (corrupted buffer list)
  * in ide_multwrite(), then we need to remove the handler/timer
  * before returning. Fortunately, this NEVER happens (right?).
  *
  * Except when you get an error it seems...
  */
  hwgroup->wrq = *rq; /* scratchpad */
  ide_set_handler (drive, &multwrite_intr, WAIT_CMD, NULL);
  if (ide_multwrite(drive, drive->mult_count)) {
  unsigned long flags;
  spin_lock_irqsave(&io_request_lock, flags);
  hwgroup->handler = NULL;
  del_timer(&hwgroup->timer);
  spin_unlock_irqrestore(&io_request_lock, flags);
  return ide_stopped;
  }
  } else {
  ide_set_handler (drive, &write_intr, WAIT_CMD, NULL);
  idedisk_output_data(drive, rq->buffer, SECTOR_WORDS); 写入一扇区SECTOR_WORDS=512/4
  }
  return ide_started;
  }
  printk(KERN_ERR "%s: bad command: %d\n", drive->name, rq->cmd);
  ide_end_request(0, HWGROUP(drive));
  return ide_stopped;
  }
  void ide_set_handler (ide_drive_t *drive, ide_handler_t *handler,
  unsigned int timeout, ide_expiry_t *expiry)
  {
  unsigned long flags;
  ide_hwgroup_t *hwgroup = HWGROUP(drive);
  spin_lock_irqsave(&io_request_lock, flags);
  if (hwgroup->handler != NULL) {
  printk("%s: ide_set_handler: handler not null; old=%p, new=%p\n",
  drive->name, hwgroup->handler, handler);
  }
  hwgroup->handler = handler;
  hwgroup->expiry = expiry;
  hwgroup->timer.expires = jiffies + timeout;
  add_timer(&hwgroup->timer);
  spin_unlock_irqrestore(&io_request_lock, flags);
  }
  static inline void idedisk_output_data (ide_drive_t *drive, void *buffer, unsigned int wcount)
  {
  if (drive->bswap) {
  idedisk_bswap_data(buffer, wcount);
  ide_output_data(drive, buffer, wcount);
  idedisk_bswap_data(buffer, wcount);
  } else
  ide_output_data(drive, buffer, wcount);
  }
  void ide_output_data (ide_drive_t *drive, void *buffer, unsigned int wcount)
  {
  byte io_32bit = drive->io_32bit;
  if (io_32bit) {
  #if SUPPORT_VLB_SYNC
  if (io_32bit & 2) {
  unsigned long flags;
  __save_flags(flags); /* local CPU only */
  __cli(); /* local CPU only */
  do_vlb_sync(IDE_NSECTOR_REG);
  outsl(IDE_DATA_REG, buffer, wcount);
  __restore_flags(flags); /* local CPU only */
  } else
  #endif /* SUPPORT_VLB_SYNC */
  outsl(IDE_DATA_REG, buffer, wcount);
  } else {
  #if SUPPORT_SLOW_DATA_PORTS
  if (drive->slow) {
  unsigned short *ptr = (unsigned short *) buffer;
  while (wcount--) {
  outw_p(*ptr++, IDE_DATA_REG);
  outw_p(*ptr++, IDE_DATA_REG);
  }
  } else
  #endif /* SUPPORT_SLOW_DATA_PORTS */
  outsw(IDE_DATA_REG, buffer, wcount<<1);
  }
  }
  int ide_multwrite (ide_drive_t *drive, unsigned int mcount)
  {
  ide_hwgroup_t *hwgroup= HWGROUP(drive);
  /*
  * This may look a bit odd, but remember wrq is a copy of the
  * request not the original. The pointers are real however so the
  * bh's are not copies. Remember that or bad stuff will happen
  *
  * At the point we are called the drive has asked us for the
  * data, and its our job to feed it, walking across bh boundaries
  * if need be.
  */
  struct request *rq = &hwgroup->wrq;
  do {
  unsigned long flags;
  unsigned int nsect = rq->current_nr_sectors;
  if (nsect > mcount)
  nsect = mcount;
  mcount -= nsect;
  ; 这时mcount为剩余的必需传送的扇区数
  idedisk_output_data(drive, rq->buffer, nsect<<7);
  spin_lock_irqsave(&io_request_lock, flags); /* Is this really necessary? */
  #ifdef CONFIG_BLK_DEV_PDC4030
  rq->sector += nsect;
  #endif
  if (((long)(rq->nr_sectors -= nsect)) <= 0)
  spin_unlock_irqrestore(&io_request_lock, flags);
  break;
  }
  if ((rq->current_nr_sectors -= nsect) == 0) {
  if ((rq->bh = rq->bh->b_reqnext) != NULL) {{
  rq->current_nr_sectors = rq->bh->b_size>>9;
  rq->buffer = rq->bh->b_data;
  } else {
  spin_unlock_irqrestore(&io_request_lock, flags);
  printk("%s: buffer list corrupted (%ld, %ld, %d)\n",
  drive->name, rq->current_nr_sectors,
  rq->nr_sectors, nsect);
  ide_end_request(0, hwgroup);
  return 1;
  }
  } else {
  /* Fix the pointer.. we ate data */
  rq->buffer += nsect << 9;
  }
  spin_unlock_irqrestore(&io_request_lock, flags);
  } while (mcount);
  return 0;
  }
  ; IDE接口共用中断入口
  void ide_intr (int irq, void *dev_id, struct pt_regs *regs)
  {
  unsigned long flags;
  ide_hwgroup_t *hwgroup = (ide_hwgroup_t *)dev_id;
  ide_hwif_t *hwif;
  ide_drive_t *drive;
  ide_handler_t *handler;
  ide_startstop_t startstop;
  spin_lock_irqsave(&io_request_lock, flags);
  hwif = hwgroup->hwif;
  if (!ide_ack_intr(hwif)) {
  spin_unlock_irqrestore(&io_request_lock, flags);
  return;
  }
  if ((handler = hwgroup->handler) == NULL || hwgroup->poll_timeout != 0) {
  /*
  * Not expecting an interrupt from this drive.
  * That means this could be:
  * (1) an interrupt from another PCI device
  * sharing the same PCI INT# as us.
  * or (2) a drive just entered sleep or standby mode,
  * and is interrupting to let us know.
  * or (3) a spurious interrupt of unknown origin.
  *
  * For PCI, we cannot tell the difference,
  * so in that case we just ignore it and hope it goes away.
  */
  #ifdef CONFIG_BLK_DEV_IDEPCI
  if (IDE_PCI_DEVID_EQ(hwif->pci_devid, IDE_PCI_DEVID_NULL))
  #endif /* CONFIG_BLK_DEV_IDEPCI */
  {
  /*
  * Probably not a shared PCI interrupt,
  * so we can safely try to do something about it:
  */
  unexpected_intr(irq, hwgroup);
  #ifdef CONFIG_BLK_DEV_IDEPCI
  } else {
  /*
  * Whack the status register, just in case we have a leftover pending IRQ.
  */
  (void) IN_BYTE(hwif->io_ports[IDE_STATUS_OFFSET]);
  #endif /* CONFIG_BLK_DEV_IDEPCI */
  }
  spin_unlock_irqrestore(&io_request_lock, flags);
  return;
  }
  drive = hwgroup->drive;
  if (!drive) {
  /*
  * This should NEVER happen, and there isn't much we could do about it here.
  */
  spin_unlock_irqrestore(&io_request_lock, flags);
  return;
  }
  if (!drive_is_ready(drive)) {
  /*
  * This happens regularly when we share a PCI IRQ with another device.
  * Unfortunately, it can also happen with some buggy drives that trigger
  * the IRQ before their status register is up to date. Hopefully we have
  * enough advance overhead that the latter isn't a problem.
  */
  spin_unlock_irqrestore(&io_request_lock, flags);
  return;
  }
  if (!hwgroup->busy) {
  hwgroup->busy = 1; /* paranoia */
  printk("%s: ide_intr: hwgroup->busy was 0 ??\n", drive->name);
  }
  hwgroup->handler = NULL;
  del_timer(&hwgroup->timer);
  spin_unlock(&io_request_lock);
  if (drive->unmask)
  ide__sti(); /* local CPU only */
  startstop = handler(drive); /* service this interrupt, may set handler for next interrupt */
  spin_lock_irq(&io_request_lock);
  /*
  * Note that handler() may have set things up for another
  * interrupt to oclearcase/" target="_blank" >ccur soon, but it cannot happen until
  * we exit from this routine, because it will be the
  * same irq as is currently being serviced here, and Linux
  * won't allow another of the same (on any CPU) until we return.
  */
  set_recovery_timer(HWIF(drive));
  drive->service_time = jiffies - drive->service_start;
  if (startstop == ide_stopped) {
  if (hwgroup->handler == NULL) { /* paranoia */
  hwgroup->busy = 0;
  ide_do_request(hwgroup, hwif->irq);
  } else {
  printk("%s: ide_intr: huh? expected NULL handler on exit\n", drive->name);
  }
  }
  spin_unlock_irqrestore(&io_request_lock, flags);
  }
  ; 单个扇区写入之后的中断处理
  static ide_startstop_t write_intr (ide_drive_t *drive)
  {
  byte stat;
  int i;
  ide_hwgroup_t *hwgroup = HWGROUP(drive);
  struct request *rq = hwgroup->rq;
  if (!OK_STAT(stat=GET_STAT(),DRIVE_READY,drive->bad_wstat)) {
  printk("%s: write_intr error1: nr_sectors=%ld, stat=0x%02x\n", drive->name, rq->nr_sectors, stat);
  } else {
  if ((rq->nr_sectors == 1) ^ ((stat & DRQ_STAT) != 0)) {
  rq->sector++;
  rq->buffer += 512;
  rq->errors = 0;
  i = --rq->nr_sectors;
  --rq->current_nr_sectors;
  if (((long)rq->current_nr_sectors) <= 0)
  ide_end_request(1, hwgroup);
  if (i > 0) {
  idedisk_output_data (drive, rq->buffer, SECTOR_WORDS);
  ide_set_handler (drive, &write_intr, WAIT_CMD, NULL);
  return ide_started;
  }
  return ide_stopped;
  }
  return ide_stopped; /* the original code did this here (?) */
  }
  return ide_error(drive, "write_intr", stat);
  }
  ; 多重扇区写入后的中断处理
  static ide_startstop_t multwrite_intr (ide_drive_t *drive)
  {
  byte stat;
  int i;
  ide_hwgroup_t *hwgroup = HWGROUP(drive);
  struct request *rq = &hwgroup->wrq;
  if (OK_STAT(stat=GET_STAT(),DRIVE_READY,drive->bad_wstat)) {
  if (stat & DRQ_STAT) {
  /*
  * The drive wants data. Remember rq is the copy
  * of the request
  */
  if (rq->nr_sectors) {
  if (ide_multwrite(drive, drive->mult_count))
  return ide_stopped;
  ide_set_handler (drive, &multwrite_intr, WAIT_CMD, NULL);
  return ide_started;
  }
  } else {
  /*
  * If the copy has all the blocks completed then
  * we can end the original request.
  */
  if (!rq->nr_sectors) { /* all done? */
  rq = hwgroup->rq;
  for (i = rq->nr_sectors; i > 0{
  i -= rq->current_nr_sectors;
  ide_end_request(1, hwgroup);
  }
  return ide_stopped;
  }
  }
  return ide_stopped; /* the original code did this here (?) */
  }
  return ide_error(drive, "multwrite_intr", stat);
  }
  ; 读扇区的中断处理
  static ide_startstop_t read_intr (ide_drive_t *drive)
  {
  byte stat;
  int i;
  unsigned int msect, nsect;
  struct request *rq;
  /* new way for dealing with premature shared PCI interrupts */
  if (!OK_STAT(stat=GET_STAT(),DATA_READY,BAD_R_STAT)) {
  if (stat & (ERR_STAT|DRQ_STAT)) {
  return ide_error(drive, "read_intr", stat);
  }
  /* no data yet, so wait for another interrupt */
  ide_set_handler(drive, &read_intr, WAIT_CMD, NULL);
  return ide_started;
  }
  msect = drive->mult_count;
  read_next:
  rq = HWGROUP(drive)->rq;
  if (msect) {
  if ((nsect = rq->current_nr_sectors) > msect)
  nsect = msect;
  msect -= nsect;
  } else
  nsect = 1;
  idedisk_input_data(drive, rq->buffer, nsect * SECTOR_WORDS);
  rq->sector += nsect;
  rq->buffer += nsect<<9;
  rq->errors = 0;
  i = (rq->nr_sectors -= nsect);
  if (((long)(rq->current_nr_sectors -= nsect)) <= 0)
  ide_end_request(1, HWGROUP(drive));
  if (i > 0) {
  if (msect)
  goto read_next;
  ide_set_handler (drive, &read_intr, WAIT_CMD, NULL);
  return ide_started;
  }
  return ide_stopped;
  }
  static inline void idedisk_input_data (ide_drive_t *drive, void *buffer, unsigned int wcount)
  {
  ide_input_data(drive, buffer, wcount);
  if (drive->bswap)
  idedisk_bswap_data(buffer, wcount);
  }
  void ide_input_data (ide_drive_t *drive, void *buffer, unsigned int wcount)
  {
  byte io_32bit = drive->io_32bit;
  if (io_32bit) {
  #if SUPPORT_VLB_SYNC
  if (io_32bit & 2) {
  unsigned long flags;
  __save_flags(flags); /* local CPU only */
  __cli(); /* local CPU only */
  do_vlb_sync(IDE_NSECTOR_REG);
  insl(IDE_DATA_REG, buffer, wcount);
  __restore_flags(flags); /* local CPU only */
  } else
  #endif /* SUPPORT_VLB_SYNC */
  insl(IDE_DATA_REG, buffer, wcount);
  } else {
  #if SUPPORT_SLOW_DATA_PORTS
  if (drive->slow) {
  unsigned short *ptr = (unsigned short *) buffer;
  while (wcount--) {
  *ptr++ = inw_p(IDE_DATA_REG);
  *ptr++ = inw_p(IDE_DATA_REG);
  }
  } else
  #endif /* SUPPORT_SLOW_DATA_PORTS */
  insw(IDE_DATA_REG, buffer, wcount<<1);
  }
  }
  atomic_t queued_sectors;
  #define blk_finished_io(nsects) \
  atomic_sub(nsects, &queued_sectors); \
  if (atomic_read(&queued_sectors) < 0) { \
  printk("block: queued_sectors < 0\n"); \
  atomic_set(&queued_sectors, 0); \
  }
  static inline void blkdev_dequeue_request(struct request * req)
  {
  list_del(&req->queue);
  }
  void ide_end_request (byte uptodate, ide_hwgroup_t *hwgroup)
  {
  struct request *rq;
  unsigned long flags;
  spin_lock_irqsave(&io_request_lock, flags);
  rq = hwgroup->rq;
  if (!end_that_request_first(rq, uptodate, hwgroup->drive->name)) {
  add_blkdev_randomness(MAJOR(rq->rq_dev));
  blkdev_dequeue_request(rq);
  hwgroup->rq = NULL;
  end_that_request_last(rq);
  }
  spin_unlock_irqrestore(&io_request_lock, flags);
  }
  int end_that_request_first (struct request *req, int uptodate, char *name)
  {
  struct buffer_head * bh;
  int nsect;
  req->errors = 0;
  if (!uptodate)
  printk("end_request: I/O error, dev %s (%s), sector %lu\n",
  kdevname(req->rq_dev), name, req->sector);
  if ((bh = req->bh) != NULL) {
  nsect = bh->b_size >> 9;
  blk_finished_io(nsect);
  req->bh = bh->b_reqnext;
  bh->b_reqnext = NULL;
  bh->b_end_io(bh, uptodate);
  if ((bh = req->bh) != NULL) {
  req->hard_sector += nsect;
  req->hard_nr_sectors -= nsect;
  req->sector = req->hard_sector;
  req->nr_sectors = req->hard_nr_sectors;
  req->current_nr_sectors = bh->b_size >> 9;
  if (req->nr_sectors < req->current_nr_sectors) {
  req->nr_sectors = req->current_nr_sectors;
  printk("end_request: buffer-list destroyed\n");
  }
  req->buffer = bh->b_data;
  return 1;
  }
  }
  return 0;
  }
  void end_that_request_last(struct request *req)
  {
  if (req->sem != NULL)
  up(req->sem);
  blkdev_release_request(req);
  }
  void inline blkdev_release_request(struct request *req)
  {
  request_queue_t *q = req->q;
  int rw = req->cmd;
  req->rq_status = RQ_INACTIVE;
  req->q = NULL;
  /*
  * Request may not have originated from ll_rw_blk. if not,
  * asumme it has free buffers and check waiters
  */
  if (q) {
  /*
  * we've released enough buffers to start I/O again
  */
  if (waitqueue_active(&blk_buffers_wait)
  && atomic_read(&queued_sectors) < low_queued_sectors)
  wake_up(&blk_buffers_wait);
  /*
  * Add to pending free list and batch wakeups
  */
  list_add(&req->table, &q->pending_freelist[rw]);
  if (++q->pending_free[rw] >= batch_requests) {
  int wake_up = q->pending_free[rw];
  blk_refill_freelist(q, rw);
  wake_up_nr(&q->wait_for_request, wake_up);
  }
  }
  }
  void inline blk_refill_freelist(request_queue_t *q, int rw)
  {
  if (q->pending_free[rw]) {
  list_splice(&q->pending_freelist[rw], &q->request_freelist[rw]);
  INIT_LIST_HEAD(&q->pending_freelist[rw]);
  q->pending_free[rw] = 0;
  }
  }
  

  
  
  

原文转自:http://www.ltesting.net