📄 sr.c
字号:
/* * sr.c Copyright (C) 1992 David Giller * Copyright (C) 1993, 1994, 1995, 1999 Eric Youngdale * * adapted from: * sd.c Copyright (C) 1992 Drew Eckhardt * Linux scsi disk driver by * Drew Eckhardt <drew@colorado.edu> * * Modified by Eric Youngdale ericy@andante.org to * add scatter-gather, multiple outstanding request, and other * enhancements. * * Modified by Eric Youngdale eric@andante.org to support loadable * low-level scsi drivers. * * Modified by Thomas Quinot thomas@melchior.cuivre.fdn.fr to * provide auto-eject. * * Modified by Gerd Knorr <kraxel@cs.tu-berlin.de> to support the * generic cdrom interface * * Modified by Jens Axboe <axboe@suse.de> - Uniform sr_packet() * interface, capabilities probe additions, ioctl cleanups, etc. * * Modified by Richard Gooch <rgooch@atnf.csiro.au> to support devfs * * Modified by Jens Axboe <axboe@suse.de> - support DVD-RAM * transparently and lose the GHOST hack * * Modified by Arnaldo Carvalho de Melo <acme@conectiva.com.br> * check resource allocation in sr_init and some cleanups */#include <linux/module.h>#include <linux/fs.h>#include <linux/kernel.h>#include <linux/mm.h>#include <linux/bio.h>#include <linux/string.h>#include <linux/errno.h>#include <linux/cdrom.h>#include <linux/interrupt.h>#include <linux/init.h>#include <linux/blkdev.h>#include <linux/mutex.h>#include <asm/uaccess.h>#include <scsi/scsi.h>#include <scsi/scsi_dbg.h>#include <scsi/scsi_device.h>#include <scsi/scsi_driver.h>#include <scsi/scsi_cmnd.h>#include <scsi/scsi_eh.h>#include <scsi/scsi_host.h>#include <scsi/scsi_ioctl.h> /* For the door lock/unlock commands */#include "scsi_logging.h"#include "sr.h"MODULE_DESCRIPTION("SCSI cdrom (sr) driver");MODULE_LICENSE("GPL");MODULE_ALIAS_BLOCKDEV_MAJOR(SCSI_CDROM_MAJOR);MODULE_ALIAS_SCSI_DEVICE(TYPE_ROM);MODULE_ALIAS_SCSI_DEVICE(TYPE_WORM);#define SR_DISKS 256#define MAX_RETRIES 3#define SR_TIMEOUT (30 * HZ)#define SR_CAPABILITIES \ (CDC_CLOSE_TRAY|CDC_OPEN_TRAY|CDC_LOCK|CDC_SELECT_SPEED| \ CDC_SELECT_DISC|CDC_MULTI_SESSION|CDC_MCN|CDC_MEDIA_CHANGED| \ CDC_PLAY_AUDIO|CDC_RESET|CDC_DRIVE_STATUS| \ CDC_CD_R|CDC_CD_RW|CDC_DVD|CDC_DVD_R|CDC_DVD_RAM|CDC_GENERIC_PACKET| \ CDC_MRW|CDC_MRW_W|CDC_RAM)static int sr_probe(struct device *);static int sr_remove(struct device *);static int sr_done(struct scsi_cmnd *);static struct scsi_driver sr_template = { .owner = THIS_MODULE, .gendrv = { .name = "sr", .probe = sr_probe, .remove = sr_remove, }, .done = sr_done,};static unsigned long sr_index_bits[SR_DISKS / BITS_PER_LONG];static DEFINE_SPINLOCK(sr_index_lock);/* This semaphore is used to mediate the 0->1 reference get in the * face of object destruction (i.e. we can't allow a get on an * object after last put) */static DEFINE_MUTEX(sr_ref_mutex);static int sr_open(struct cdrom_device_info *, int);static void sr_release(struct cdrom_device_info *);static void get_sectorsize(struct scsi_cd *);static void get_capabilities(struct scsi_cd *);static int sr_media_change(struct cdrom_device_info *, int);static int sr_packet(struct cdrom_device_info *, struct packet_command *);static struct cdrom_device_ops sr_dops = { .open = sr_open, .release = sr_release, .drive_status = sr_drive_status, .media_changed = sr_media_change, .tray_move = sr_tray_move, .lock_door = sr_lock_door, .select_speed = sr_select_speed, .get_last_session = sr_get_last_session, .get_mcn = sr_get_mcn, .reset = sr_reset, .audio_ioctl = sr_audio_ioctl, .capability = SR_CAPABILITIES, .generic_packet = sr_packet,};static void sr_kref_release(struct kref *kref);static inline struct scsi_cd *scsi_cd(struct gendisk *disk){ return container_of(disk->private_data, struct scsi_cd, driver);}/* * The get and put routines for the struct scsi_cd. Note this entity * has a scsi_device pointer and owns a reference to this. */static inline struct scsi_cd *scsi_cd_get(struct gendisk *disk){ struct scsi_cd *cd = NULL; mutex_lock(&sr_ref_mutex); if (disk->private_data == NULL) goto out; cd = scsi_cd(disk); kref_get(&cd->kref); if (scsi_device_get(cd->device)) goto out_put; goto out; out_put: kref_put(&cd->kref, sr_kref_release); cd = NULL; out: mutex_unlock(&sr_ref_mutex); return cd;}static void scsi_cd_put(struct scsi_cd *cd){ struct scsi_device *sdev = cd->device; mutex_lock(&sr_ref_mutex); kref_put(&cd->kref, sr_kref_release); scsi_device_put(sdev); mutex_unlock(&sr_ref_mutex);}/* * This function checks to see if the media has been changed in the * CDROM drive. It is possible that we have already sensed a change, * or the drive may have sensed one and not yet reported it. We must * be ready for either case. This function always reports the current * value of the changed bit. If flag is 0, then the changed bit is reset. * This function could be done as an ioctl, but we would need to have * an inode for that to work, and we do not always have one. */static int sr_media_change(struct cdrom_device_info *cdi, int slot){ struct scsi_cd *cd = cdi->handle; int retval; if (CDSL_CURRENT != slot) { /* no changer support */ return -EINVAL; } retval = scsi_test_unit_ready(cd->device, SR_TIMEOUT, MAX_RETRIES); if (retval) { /* Unable to test, unit probably not ready. This usually * means there is no disc in the drive. Mark as changed, * and we will figure it out later once the drive is * available again. */ cd->device->changed = 1; return 1; /* This will force a flush, if called from * check_disk_change */ }; retval = cd->device->changed; cd->device->changed = 0; /* If the disk changed, the capacity will now be different, * so we force a re-read of this information */ if (retval) { /* check multisession offset etc */ sr_cd_check(cdi); get_sectorsize(cd); } return retval;} /* * sr_done is the interrupt routine for the device driver. * * It will be notified on the end of a SCSI read / write, and will take one * of several actions based on success or failure. */static int sr_done(struct scsi_cmnd *SCpnt){ int result = SCpnt->result; int this_count = SCpnt->request_bufflen; int good_bytes = (result == 0 ? this_count : 0); int block_sectors = 0; long error_sector; struct scsi_cd *cd = scsi_cd(SCpnt->request->rq_disk);#ifdef DEBUG printk("sr.c done: %x\n", result);#endif /* * Handle MEDIUM ERRORs or VOLUME OVERFLOWs that indicate partial * success. Since this is a relatively rare error condition, no * care is taken to avoid unnecessary additional work such as * memcpy's that could be avoided. */ if (driver_byte(result) != 0 && /* An error occurred */ (SCpnt->sense_buffer[0] & 0x7f) == 0x70) { /* Sense current */ switch (SCpnt->sense_buffer[2]) { case MEDIUM_ERROR: case VOLUME_OVERFLOW: case ILLEGAL_REQUEST: if (!(SCpnt->sense_buffer[0] & 0x90)) break; error_sector = (SCpnt->sense_buffer[3] << 24) | (SCpnt->sense_buffer[4] << 16) | (SCpnt->sense_buffer[5] << 8) | SCpnt->sense_buffer[6]; if (SCpnt->request->bio != NULL) block_sectors = bio_sectors(SCpnt->request->bio); if (block_sectors < 4) block_sectors = 4; if (cd->device->sector_size == 2048) error_sector <<= 2; error_sector &= ~(block_sectors - 1); good_bytes = (error_sector - SCpnt->request->sector) << 9; if (good_bytes < 0 || good_bytes >= this_count) good_bytes = 0; /* * The SCSI specification allows for the value * returned by READ CAPACITY to be up to 75 2K * sectors past the last readable block. * Therefore, if we hit a medium error within the * last 75 2K sectors, we decrease the saved size * value. */ if (error_sector < get_capacity(cd->disk) && cd->capacity - error_sector < 4 * 75) set_capacity(cd->disk, error_sector); break; case RECOVERED_ERROR: /* * An error occured, but it recovered. Inform the * user, but make sure that it's not treated as a * hard error. */ scsi_print_sense("sr", SCpnt); SCpnt->result = 0; SCpnt->sense_buffer[0] = 0x0; good_bytes = this_count; break; default: break; } } return good_bytes;}static int sr_prep_fn(struct request_queue *q, struct request *rq){ int block=0, this_count, s_size, timeout = SR_TIMEOUT; struct scsi_cd *cd; struct scsi_cmnd *SCpnt; struct scsi_device *sdp = q->queuedata; int ret; if (rq->cmd_type == REQ_TYPE_BLOCK_PC) { ret = scsi_setup_blk_pc_cmnd(sdp, rq); goto out; } else if (rq->cmd_type != REQ_TYPE_FS) { ret = BLKPREP_KILL; goto out; } ret = scsi_setup_fs_cmnd(sdp, rq); if (ret != BLKPREP_OK) goto out; SCpnt = rq->special; cd = scsi_cd(rq->rq_disk); /* from here on until we're complete, any goto out * is used for a killable error condition */ ret = BLKPREP_KILL; SCSI_LOG_HLQUEUE(1, printk("Doing sr request, dev = %s, block = %d\n", cd->disk->disk_name, block)); if (!cd->device || !scsi_device_online(cd->device)) { SCSI_LOG_HLQUEUE(2, printk("Finishing %ld sectors\n", rq->nr_sectors)); SCSI_LOG_HLQUEUE(2, printk("Retry with 0x%p\n", SCpnt)); goto out; } if (cd->device->changed) { /* * quietly refuse to do anything to a changed disc until the * changed bit has been reset */ goto out; } /* * we do lazy blocksize switching (when reading XA sectors, * see CDROMREADMODE2 ioctl) */ s_size = cd->device->sector_size; if (s_size > 2048) { if (!in_interrupt()) sr_set_blocklength(cd, 2048); else printk("sr: can't switch blocksize: in interrupt\n"); } if (s_size != 512 && s_size != 1024 && s_size != 2048) { scmd_printk(KERN_ERR, SCpnt, "bad sector size %d\n", s_size); goto out; } if (rq_data_dir(rq) == WRITE) { if (!cd->device->writeable) goto out; SCpnt->cmnd[0] = WRITE_10; SCpnt->sc_data_direction = DMA_TO_DEVICE; cd->cdi.media_written = 1; } else if (rq_data_dir(rq) == READ) { SCpnt->cmnd[0] = READ_10; SCpnt->sc_data_direction = DMA_FROM_DEVICE; } else { blk_dump_rq_flags(rq, "Unknown sr command"); goto out; } { struct scatterlist *sg = SCpnt->request_buffer; int i, size = 0; for (i = 0; i < SCpnt->use_sg; i++) size += sg[i].length; if (size != SCpnt->request_bufflen && SCpnt->use_sg) { scmd_printk(KERN_ERR, SCpnt, "mismatch count %d, bytes %d\n", size, SCpnt->request_bufflen); if (SCpnt->request_bufflen > size) SCpnt->request_bufflen = size; } } /* * request doesn't start on hw block boundary, add scatter pads */ if (((unsigned int)rq->sector % (s_size >> 9)) || (SCpnt->request_bufflen % s_size)) { scmd_printk(KERN_NOTICE, SCpnt, "unaligned transfer\n"); goto out; } this_count = (SCpnt->request_bufflen >> 9) / (s_size >> 9); SCSI_LOG_HLQUEUE(2, printk("%s : %s %d/%ld 512 byte blocks.\n", cd->cdi.name, (rq_data_dir(rq) == WRITE) ? "writing" : "reading", this_count, rq->nr_sectors)); SCpnt->cmnd[1] = 0; block = (unsigned int)rq->sector / (s_size >> 9); if (this_count > 0xffff) { this_count = 0xffff; SCpnt->request_bufflen = this_count * s_size; } SCpnt->cmnd[2] = (unsigned char) (block >> 24) & 0xff; SCpnt->cmnd[3] = (unsigned char) (block >> 16) & 0xff; SCpnt->cmnd[4] = (unsigned char) (block >> 8) & 0xff; SCpnt->cmnd[5] = (unsigned char) block & 0xff; SCpnt->cmnd[6] = SCpnt->cmnd[9] = 0; SCpnt->cmnd[7] = (unsigned char) (this_count >> 8) & 0xff; SCpnt->cmnd[8] = (unsigned char) this_count & 0xff; /* * We shouldn't disconnect in the middle of a sector, so with a dumb * host adapter, it's safe to assume that we can at least transfer * this many bytes between each connect / disconnect. */ SCpnt->transfersize = cd->device->sector_size; SCpnt->underflow = this_count << 9; SCpnt->allowed = MAX_RETRIES; SCpnt->timeout_per_command = timeout; /* * This indicates that the command is ready from our end to be * queued. */ ret = BLKPREP_OK; out: return scsi_prep_return(q, rq, ret);}static int sr_block_open(struct inode *inode, struct file *file){ struct gendisk *disk = inode->i_bdev->bd_disk; struct scsi_cd *cd; int ret = 0; if(!(cd = scsi_cd_get(disk))) return -ENXIO; if((ret = cdrom_open(&cd->cdi, inode, file)) != 0) scsi_cd_put(cd); return ret;}static int sr_block_release(struct inode *inode, struct file *file){ int ret; struct scsi_cd *cd = scsi_cd(inode->i_bdev->bd_disk); ret = cdrom_release(&cd->cdi, file); if(ret) return ret;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -