📄 acsi.c
字号:
int acsi_getstatus( void ){ int status; DISABLE_IRQ(); for(;;) { if (!acsi_wait_for_IRQ( 100 )) { acsi_delay_start(); return( -1 ); } dma_wd.dma_mode_status = 0x8a; MFPDELAY(); status = dma_wd.fdc_acces_seccount; if (status != 0xff) break;#ifdef DEBUG printk("ACSI: skipping 0xff status byte\n" );#endif udelay(40); acsi_wait_for_noIRQ( 20 ); } dma_wd.dma_mode_status = 0x80; udelay(40); acsi_wait_for_noIRQ( 20 ); acsi_delay_start(); return( status & 0x1f ); /* mask of the device# */}#if (defined(CONFIG_ATARI_SLM) || defined(CONFIG_ATARI_SLM_MODULE))/* Receive data in an extended status phase. Needed by SLM printer. */int acsi_extstatus( char *buffer, int cnt ){ int status; DISABLE_IRQ(); udelay(80); while( cnt-- > 0 ) { if (!acsi_wait_for_IRQ( 40 )) return( 0 ); dma_wd.dma_mode_status = 0x8a; MFPDELAY(); status = dma_wd.fdc_acces_seccount; MFPDELAY(); *buffer++ = status & 0xff; udelay(40); } return( 1 );}/* Finish an extended status phase */void acsi_end_extstatus( void ){ dma_wd.dma_mode_status = 0x80; udelay(40); acsi_wait_for_noIRQ( 20 ); acsi_delay_start();}/* Send data in an extended command phase */int acsi_extcmd( unsigned char *buffer, int cnt ){ while( cnt-- > 0 ) { DMA_LONG_WRITE( *buffer++, 0x8a ); udelay(20); if (!acsi_wait_for_IRQ( HZ/2 )) return( 0 ); /* timeout */ } return( 1 );}#endifstatic void acsi_print_error(const unsigned char *errblk, struct acsi_info_struct *aip){ int atari_err, i, errcode; struct acsi_error *arr; atari_err = aip->old_atari_disk; if (atari_err) errcode = errblk[0] & 0x7f; else if ((errblk[0] & 0x70) == 0x70) errcode = errblk[2] & 0x0f; else errcode = errblk[0] & 0x0f; printk( KERN_ERR "ACSI error 0x%02x", errcode ); if (errblk[0] & 0x80) printk( " for sector %d", ((errblk[1] & 0x1f) << 16) | (errblk[2] << 8) | errblk[0] ); arr = atari_err ? atari_acsi_errors : scsi_acsi_errors; i = atari_err ? sizeof(atari_acsi_errors)/sizeof(*atari_acsi_errors) : sizeof(scsi_acsi_errors)/sizeof(*scsi_acsi_errors); for( --i; i >= 0; --i ) if (arr[i].code == errcode) break; if (i >= 0) printk( ": %s\n", arr[i].text );}/******************************************************************* * * ACSI interrupt routine * Test, if this is a ACSI interrupt and call the irq handler * Otherwise ignore this interrupt. * *******************************************************************/static irqreturn_t acsi_interrupt(int irq, void *data, struct pt_regs *fp ){ void (*acsi_irq_handler)(void) = do_acsi; do_acsi = NULL; CLEAR_TIMER(); if (!acsi_irq_handler) acsi_irq_handler = unexpected_acsi_interrupt; acsi_irq_handler(); return IRQ_HANDLED;}/****************************************************************** * * The Interrupt handlers * *******************************************************************/static void unexpected_acsi_interrupt( void ){ printk( KERN_WARNING "Unexpected ACSI interrupt\n" );}/* This function is called in case of errors. Because we cannot reset * the ACSI bus or a single device, there is no other choice than * retrying several times :-( */static void bad_rw_intr( void ){ if (!CURRENT) return; if (++CURRENT->errors >= MAX_ERRORS) end_request(CURRENT, 0); /* Otherwise just retry */}static void read_intr( void ){ int status; status = acsi_getstatus(); if (status != 0) { struct gendisk *disk = CURRENT->rq_disk; struct acsi_info_struct *aip = disk->private_data; printk(KERN_ERR "%s: ", disk->disk_name); if (!acsi_reqsense(acsi_buffer, aip->target, aip->lun)) printk( "ACSI error and REQUEST SENSE failed (status=0x%02x)\n", status ); else { acsi_print_error(acsi_buffer, aip); if (CARTRCH_STAT(aip, acsi_buffer)) aip->changed = 1; } ENABLE_IRQ(); bad_rw_intr(); redo_acsi_request(); return; } dma_cache_maintenance( virt_to_phys(CurrentBuffer), CurrentNSect*512, 0 ); if (CurrentBuffer == acsi_buffer) copy_from_acsibuffer(); do_end_requests(); redo_acsi_request();}static void write_intr(void){ int status; status = acsi_getstatus(); if (status != 0) { struct gendisk *disk = CURRENT->rq_disk; struct acsi_info_struct *aip = disk->private_data; printk( KERN_ERR "%s: ", disk->disk_name); if (!acsi_reqsense( acsi_buffer, aip->target, aip->lun)) printk( "ACSI error and REQUEST SENSE failed (status=0x%02x)\n", status ); else { acsi_print_error(acsi_buffer, aip); if (CARTRCH_STAT(aip, acsi_buffer)) aip->changed = 1; } bad_rw_intr(); redo_acsi_request(); return; } do_end_requests(); redo_acsi_request();}static void acsi_times_out( unsigned long dummy ){ DISABLE_IRQ(); if (!do_acsi) return; do_acsi = NULL; printk( KERN_ERR "ACSI timeout\n" ); if (!CURRENT) return; if (++CURRENT->errors >= MAX_ERRORS) {#ifdef DEBUG printk( KERN_ERR "ACSI: too many errors.\n" );#endif end_request(CURRENT, 0); } redo_acsi_request();}/*********************************************************************** * * Scatter-gather utility functions * ***********************************************************************/static void copy_to_acsibuffer( void ){ int i; char *src, *dst; struct buffer_head *bh; src = CURRENT->buffer; dst = acsi_buffer; bh = CURRENT->bh; if (!bh) memcpy( dst, src, CurrentNSect*512 ); else for( i = 0; i < CurrentNReq; ++i ) { memcpy( dst, src, bh->b_size ); dst += bh->b_size; if ((bh = bh->b_reqnext)) src = bh->b_data; }}static void copy_from_acsibuffer( void ){ int i; char *src, *dst; struct buffer_head *bh; dst = CURRENT->buffer; src = acsi_buffer; bh = CURRENT->bh; if (!bh) memcpy( dst, src, CurrentNSect*512 ); else for( i = 0; i < CurrentNReq; ++i ) { memcpy( dst, src, bh->b_size ); src += bh->b_size; if ((bh = bh->b_reqnext)) dst = bh->b_data; }}static void do_end_requests( void ){ int i, n; if (!CURRENT->bh) { CURRENT->nr_sectors -= CurrentNSect; CURRENT->current_nr_sectors -= CurrentNSect; CURRENT->sector += CurrentNSect; if (CURRENT->nr_sectors == 0) end_request(CURRENT, 1); } else { for( i = 0; i < CurrentNReq; ++i ) { n = CURRENT->bh->b_size >> 9; CURRENT->nr_sectors -= n; CURRENT->current_nr_sectors -= n; CURRENT->sector += n; end_request(CURRENT, 1); } }}/*********************************************************************** * * do_acsi_request and friends * ***********************************************************************/static void do_acsi_request( request_queue_t * q ){ stdma_lock( acsi_interrupt, NULL ); redo_acsi_request();}static void redo_acsi_request( void ){ unsigned block, target, lun, nsect; char *buffer; unsigned long pbuffer; struct buffer_head *bh; struct gendisk *disk; struct acsi_info_struct *aip; repeat: CLEAR_TIMER(); if (do_acsi) return; if (!CURRENT) { do_acsi = NULL; ENABLE_IRQ(); stdma_release(); return; } disk = CURRENT->rq_disk; aip = disk->private_data; if (CURRENT->bh) { if (!CURRENT->bh && !buffer_locked(CURRENT->bh)) panic("ACSI: block not locked"); } block = CURRENT->sector; if (block+CURRENT->nr_sectors >= get_capacity(disk)) {#ifdef DEBUG printk( "%s: attempted access for blocks %d...%ld past end of device at block %ld.\n", disk->disk_name, block, block + CURRENT->nr_sectors - 1, get_capacity(disk));#endif end_request(CURRENT, 0); goto repeat; } if (aip->changed) { printk( KERN_NOTICE "%s: request denied because cartridge has " "been changed.\n", disk->disk_name); end_request(CURRENT, 0); goto repeat; } target = aip->target; lun = aip->lun; /* Find out how many sectors should be transferred from/to * consecutive buffers and thus can be done with a single command. */ buffer = CURRENT->buffer; pbuffer = virt_to_phys(buffer); nsect = CURRENT->current_nr_sectors; CurrentNReq = 1; if ((bh = CURRENT->bh) && bh != CURRENT->bhtail) { if (!STRAM_ADDR(pbuffer)) { /* If transfer is done via the ACSI buffer anyway, we can * assemble as much bh's as fit in the buffer. */ while( (bh = bh->b_reqnext) ) { if (nsect + (bh->b_size>>9) > ACSI_BUFFER_SECTORS) break; nsect += bh->b_size >> 9; ++CurrentNReq; if (bh == CURRENT->bhtail) break; } buffer = acsi_buffer; pbuffer = phys_acsi_buffer; } else { unsigned long pendadr, pnewadr; pendadr = pbuffer + nsect*512; while( (bh = bh->b_reqnext) ) { pnewadr = virt_to_phys(bh->b_data); if (!STRAM_ADDR(pnewadr) || pendadr != pnewadr) break; nsect += bh->b_size >> 9; pendadr = pnewadr + bh->b_size; ++CurrentNReq; if (bh == CURRENT->bhtail) break; } } } else { if (!STRAM_ADDR(pbuffer)) { buffer = acsi_buffer; pbuffer = phys_acsi_buffer; if (nsect > ACSI_BUFFER_SECTORS) nsect = ACSI_BUFFER_SECTORS; } } CurrentBuffer = buffer; CurrentNSect = nsect; if (rq_data_dir(CURRENT) == WRITE) { CMDSET_TARG_LUN( write_cmd, target, lun ); CMDSET_BLOCK( write_cmd, block ); CMDSET_LEN( write_cmd, nsect ); if (buffer == acsi_buffer) copy_to_acsibuffer(); dma_cache_maintenance( pbuffer, nsect*512, 1 ); do_acsi = write_intr; if (!acsicmd_dma( write_cmd, buffer, nsect, 1, 1)) { do_acsi = NULL; printk( KERN_ERR "ACSI (write): Timeout in command block\n" ); bad_rw_intr(); goto repeat; } SET_TIMER(); return; } if (rq_data_dir(CURRENT) == READ) { CMDSET_TARG_LUN( read_cmd, target, lun ); CMDSET_BLOCK( read_cmd, block ); CMDSET_LEN( read_cmd, nsect ); do_acsi = read_intr; if (!acsicmd_dma( read_cmd, buffer, nsect, 0, 1)) { do_acsi = NULL; printk( KERN_ERR "ACSI (read): Timeout in command block\n" ); bad_rw_intr(); goto repeat; } SET_TIMER(); return; } panic("unknown ACSI command");}/*********************************************************************** * * Misc functions: ioctl, open, release, check_change, ... * ***********************************************************************/static int acsi_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg ){ struct gendisk *disk = inode->i_bdev->bd_disk; struct acsi_info_struct *aip = disk->private_data; switch (cmd) { case HDIO_GETGEO: /* HDIO_GETGEO is supported more for getting the partition's * start sector... */ { struct hd_geometry *geo = (struct hd_geometry *)arg; /* just fake some geometry here, it's nonsense anyway; to make it * easy, use Adaptec's usual 64/32 mapping */ put_user( 64, &geo->heads ); put_user( 32, &geo->sectors ); put_user( aip->size >> 11, &geo->cylinders ); put_user(get_start_sect(inode->i_bdev), &geo->start); return 0; } case SCSI_IOCTL_GET_IDLUN: /* SCSI compatible GET_IDLUN call to get target's ID and LUN number */ put_user( aip->target | (aip->lun << 8), &((Scsi_Idlun *) arg)->dev_id ); put_user( 0, &((Scsi_Idlun *) arg)->host_unique_id ); return 0; default: return -EINVAL; }}/* * Open a device, check for read-only and lock the medium if it is * removable. * * Changes by Martin Rogge, 9th Aug 1995: * Check whether check_disk_change (and therefore revalidate_acsidisk) * was successful. They fail when there is no medium in the drive. * * The problem of media being changed during an operation can be * ignored because of the prevent_removal code. * * Added check for the validity of the device number. * */static int acsi_open( struct inode * inode, struct file * filp ){ struct gendisk *disk = inode->i_bdev->bd_disk; struct acsi_info_struct *aip = disk->private_data; if (aip->access_count == 0 && aip->removable) {#if 0 aip->changed = 1; /* safety first */#endif check_disk_change( inode->i_bdev ); if (aip->changed) /* revalidate was not successful (no medium) */ return -ENXIO; acsi_prevent_removal(aip, 1); } aip->access_count++; if (filp && filp->f_mode) { check_disk_change( inode->i_bdev ); if (filp->f_mode & 2) { if (aip->read_only) { acsi_release( inode, filp ); return -EROFS; } } } return 0;}/* * Releasing a block device means we sync() it, so that it can safely * be forgotten about... */static int acsi_release( struct inode * inode, struct file * file ){ struct gendisk *disk = inode->i_bdev->bd_disk; struct acsi_info_struct *aip = disk->private_data; if (--aip->access_count == 0 && aip->removable) acsi_prevent_removal(aip, 0); return( 0 );}/* * Prevent or allow a media change for removable devices. */static void acsi_prevent_removal(struct acsi_info_struct *aip, int flag){ stdma_lock( NULL, NULL ); CMDSET_TARG_LUN(pa_med_rem_cmd, aip->target, aip->lun); CMDSET_LEN( pa_med_rem_cmd, flag ); if (acsicmd_nodma(pa_med_rem_cmd, 0) && acsi_wait_for_IRQ(3*HZ)) acsi_getstatus(); /* Do not report errors -- some devices may not know this command. */ ENABLE_IRQ(); stdma_release();}static int acsi_media_change(struct gendisk *disk){ struct acsi_info_struct *aip = disk->private_data; if (!aip->removable) return 0; if (aip->changed) /* We can be sure that the medium has been changed -- REQUEST * SENSE has reported this earlier. */ return 1; /* If the flag isn't set, make a test by reading block 0. * If errors happen, it seems to be better to say "changed"... */ stdma_lock( NULL, NULL ); CMDSET_TARG_LUN(read_cmd, aip->target, aip->lun); CMDSET_BLOCK( read_cmd, 0 ); CMDSET_LEN( read_cmd, 1 ); if (acsicmd_dma(read_cmd, acsi_buffer, 1, 0, 0) && acsi_wait_for_IRQ(3*HZ)) { if (acsi_getstatus()) { if (acsi_reqsense(acsi_buffer, aip->target, aip->lun)) { if (CARTRCH_STAT(aip, acsi_buffer)) aip->changed = 1; } else { printk( KERN_ERR "%s: REQUEST SENSE failed in test for " "medium change; assuming a change\n", disk->disk_name );
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -