当前位置:Linux教程 - Linux - 分析IDE硬盘驱动器读写过程

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



        

    原作者: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.cstatic 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>>8)&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 * bhs 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 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 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 occur 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 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) { /* * weve 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; }}


    发布人:netbull 来自:中国Linux论坛