📄 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/sched.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 <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_eh.h>#include <scsi/scsi_host.h>#include <scsi/scsi_ioctl.h> /* For the door lock/unlock commands */#include <scsi/scsi_request.h>#include "scsi_logging.h"#include "sr.h"MODULE_PARM(xa_test, "i"); /* see sr_ioctl.c */#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_IOCTLS|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_init_command(struct scsi_cmnd *);static struct scsi_driver sr_template = { .owner = THIS_MODULE, .gendrv = { .name = "sr", .probe = sr_probe, .remove = sr_remove, }, .init_command = sr_init_command,};static unsigned long sr_index_bits[SR_DISKS / BITS_PER_LONG];static spinlock_t sr_index_lock = SPIN_LOCK_UNLOCKED;/* 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 DECLARE_MUTEX(sr_ref_sem);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, .dev_ioctl = sr_dev_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; down(&sr_ref_sem); 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: up(&sr_ref_sem); return cd;}static inline void scsi_cd_put(struct scsi_cd *cd){ down(&sr_ref_sem); scsi_device_put(cd->device); kref_put(&cd->kref, sr_kref_release); up(&sr_ref_sem);}/* * 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. */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); /* * If the disk changed, the capacity will now be different, * so we force a re-read of this information * Force 2048 for the sector size so that filesystems won't * be trying to use something that is too small if the disc * has changed. */ cd->needs_sector_size = 1; cd->device->sector_size = 2048; } return retval;} /* * rw_intr is the interrupt routine for the device driver. * * It will be notified on the end of a SCSI read / write, and will take on * of several actions based on success or failure. */static void rw_intr(struct scsi_cmnd * SCpnt){ int result = SCpnt->result; int this_count = SCpnt->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; if (!blk_fs_request(SCpnt->request)) 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; } } /* * This calls the generic completion function, now that we know * how many actual sectors finished, and how many sectors we need * to say have failed. */ scsi_io_completion(SCpnt, good_bytes, block_sectors << 9);}static int sr_init_command(struct scsi_cmnd * SCpnt){ int block=0, this_count, s_size, timeout = SR_TIMEOUT; struct scsi_cd *cd = scsi_cd(SCpnt->request->rq_disk); 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", SCpnt->request->nr_sectors)); SCSI_LOG_HLQUEUE(2, printk("Retry with 0x%p\n", SCpnt)); return 0; } if (cd->device->changed) { /* * quietly refuse to do anything to a changed disc until the * changed bit has been reset */ return 0; } /* * these are already setup, just copy cdb basically */ if (SCpnt->request->flags & REQ_BLOCK_PC) { struct request *rq = SCpnt->request; if (sizeof(rq->cmd) > sizeof(SCpnt->cmnd)) return 0; memcpy(SCpnt->cmnd, rq->cmd, sizeof(SCpnt->cmnd)); if (!rq->data_len) SCpnt->sc_data_direction = DMA_NONE; else if (rq_data_dir(rq) == WRITE) SCpnt->sc_data_direction = DMA_TO_DEVICE; else SCpnt->sc_data_direction = DMA_FROM_DEVICE; this_count = rq->data_len; if (rq->timeout) timeout = rq->timeout; SCpnt->transfersize = rq->data_len; goto queue; } if (!(SCpnt->request->flags & REQ_CMD)) { blk_dump_rq_flags(SCpnt->request, "sr unsup command"); return 0; } /* * 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) { printk("sr: bad sector size %d\n", s_size); return 0; } if (rq_data_dir(SCpnt->request) == WRITE) { if (!cd->device->writeable) return 0; SCpnt->cmnd[0] = WRITE_10; SCpnt->sc_data_direction = DMA_TO_DEVICE; } else if (rq_data_dir(SCpnt->request) == READ) { SCpnt->cmnd[0] = READ_10; SCpnt->sc_data_direction = DMA_FROM_DEVICE; } else { blk_dump_rq_flags(SCpnt->request, "Unknown sr command"); return 0; } { 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) { printk(KERN_ERR "sr: mismatch count %d, bytes %d\n", size, SCpnt->request_bufflen); if (SCpnt->request_bufflen > size) SCpnt->request_bufflen = SCpnt->bufflen = size; } } /* * request doesn't start on hw block boundary, add scatter pads */ if (((unsigned int)SCpnt->request->sector % (s_size >> 9)) || (SCpnt->request_bufflen % s_size)) { printk("sr: unaligned transfer\n"); return 0; } 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(SCpnt->request) == WRITE) ? "writing" : "reading", this_count, SCpnt->request->nr_sectors)); SCpnt->cmnd[1] = 0; block = (unsigned int)SCpnt->request->sector / (s_size >> 9); if (this_count > 0xffff) { this_count = 0xffff; SCpnt->request_bufflen = SCpnt->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;queue: SCpnt->allowed = MAX_RETRIES; SCpnt->timeout_per_command = timeout; /* * This is the completion routine we use. This is matched in terms * of capability to this function. */ SCpnt->done = rw_intr; /* * This indicates that the command is ready from our end to be * queued. */ return 1;}static int sr_block_open(struct inode *inode, struct file *file){ struct gendisk *disk = inode->i_bdev->bd_disk; struct scsi_cd *cd = scsi_cd(inode->i_bdev->bd_disk); 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)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -