summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/cdrom/cdrom.c185
-rw-r--r--drivers/ide/ide-cd.c1
-rw-r--r--drivers/scsi/sr.c5
-rw-r--r--include/linux/cdrom.h10
4 files changed, 165 insertions, 36 deletions
diff --git a/drivers/cdrom/cdrom.c b/drivers/cdrom/cdrom.c
index 79974e6af42e..11bbaffdc9f2 100644
--- a/drivers/cdrom/cdrom.c
+++ b/drivers/cdrom/cdrom.c
@@ -406,6 +406,11 @@ int register_cdrom(struct cdrom_device_info *cdi)
if (CDROM_CAN(CDC_MRW_W))
cdi->exit = cdrom_mrw_exit;
+ if (cdi->disk)
+ cdi->cdda_method = CDDA_BPC_FULL;
+ else
+ cdi->cdda_method = CDDA_OLD;
+
cdinfo(CD_REG_UNREG, "drive \"/dev/%s\" registered\n", cdi->name);
spin_lock(&cdrom_lock);
cdi->next = topCdromPtr;
@@ -1788,6 +1793,149 @@ static int cdrom_read_block(struct cdrom_device_info *cdi,
return cdo->generic_packet(cdi, cgc);
}
+static int cdrom_read_cdda_old(struct cdrom_device_info *cdi, __u8 __user *ubuf,
+ int lba, int nframes)
+{
+ struct cdrom_generic_command cgc;
+ int nr, ret;
+
+ memset(&cgc, 0, sizeof(cgc));
+
+ /*
+ * start with will ra.nframes size, back down if alloc fails
+ */
+ nr = nframes;
+ do {
+ cgc.buffer = kmalloc(CD_FRAMESIZE_RAW * nr, GFP_KERNEL);
+ if (cgc.buffer)
+ break;
+
+ nr >>= 1;
+ } while (nr);
+
+ if (!nr)
+ return -ENOMEM;
+
+ if (!access_ok(VERIFY_WRITE, ubuf, nframes * CD_FRAMESIZE_RAW)) {
+ kfree(cgc.buffer);
+ return -EFAULT;
+ }
+
+ cgc.data_direction = CGC_DATA_READ;
+ while (nframes > 0) {
+ if (nr > nframes)
+ nr = nframes;
+
+ ret = cdrom_read_block(cdi, &cgc, lba, nr, 1, CD_FRAMESIZE_RAW);
+ if (ret)
+ break;
+ __copy_to_user(ubuf, cgc.buffer, CD_FRAMESIZE_RAW * nr);
+ ubuf += CD_FRAMESIZE_RAW * nr;
+ nframes -= nr;
+ lba += nr;
+ }
+ kfree(cgc.buffer);
+ return 0;
+}
+
+static int cdrom_read_cdda_bpc(struct cdrom_device_info *cdi, __u8 __user *ubuf,
+ int lba, int nframes)
+{
+ request_queue_t *q = cdi->disk->queue;
+ struct request *rq;
+ unsigned int len;
+ int nr, ret = 0;
+
+ if (!q)
+ return -ENXIO;
+
+ while (nframes) {
+ nr = nframes;
+ if (cdi->cdda_method == CDDA_BPC_SINGLE)
+ nr = 1;
+ if (nr * CD_FRAMESIZE_RAW > (q->max_sectors << 9))
+ nr = (q->max_sectors << 9) / CD_FRAMESIZE_RAW;
+
+ len = nr * CD_FRAMESIZE_RAW;
+
+ rq = blk_rq_map_user(q, READ, ubuf, len);
+ if (IS_ERR(rq))
+ return PTR_ERR(rq);
+
+ memset(rq->cmd, 0, sizeof(rq->cmd));
+ rq->cmd[0] = GPCMD_READ_CD;
+ rq->cmd[1] = 1 << 2;
+ rq->cmd[2] = (lba >> 24) & 0xff;
+ rq->cmd[3] = (lba >> 16) & 0xff;
+ rq->cmd[4] = (lba >> 8) & 0xff;
+ rq->cmd[5] = lba & 0xff;
+ rq->cmd[6] = (nr >> 16) & 0xff;
+ rq->cmd[7] = (nr >> 8) & 0xff;
+ rq->cmd[8] = nr & 0xff;
+ rq->cmd[9] = 0xf8;
+
+ rq->cmd_len = 12;
+ rq->flags |= REQ_BLOCK_PC;
+ rq->timeout = 60 * HZ;
+
+ if (blk_execute_rq(q, cdi->disk, rq)) {
+ struct request_sense *s = rq->sense;
+ ret = -EIO;
+ cdi->last_sense = s->sense_key;
+ }
+
+ if (blk_rq_unmap_user(rq, ubuf, len))
+ ret = -EFAULT;
+
+ if (ret)
+ break;
+
+ nframes -= nr;
+ lba += nr;
+ }
+
+ return ret;
+}
+
+static int cdrom_read_cdda(struct cdrom_device_info *cdi, __u8 __user *ubuf,
+ int lba, int nframes)
+{
+ int ret;
+
+ if (cdi->cdda_method == CDDA_OLD)
+ return cdrom_read_cdda_old(cdi, ubuf, lba, nframes);
+
+retry:
+ /*
+ * for anything else than success and io error, we need to retry
+ */
+ ret = cdrom_read_cdda_bpc(cdi, ubuf, lba, nframes);
+ if (!ret || ret != -EIO)
+ return ret;
+
+ /*
+ * I've seen drives get sense 4/8/3 udma crc errors on multi
+ * frame dma, so drop to single frame dma if we need to
+ */
+ if (cdi->cdda_method == CDDA_BPC_FULL && nframes > 1) {
+ printk("cdrom: dropping to single frame dma\n");
+ cdi->cdda_method = CDDA_BPC_SINGLE;
+ goto retry;
+ }
+
+ /*
+ * so we have an io error of some sort with multi frame dma. if the
+ * condition wasn't a hardware error
+ * problems, not for any error
+ */
+ if (cdi->last_sense != 0x04 && cdi->last_sense != 0x0b)
+ return ret;
+
+ printk("cdrom: dropping to old style cdda (sense=%x)\n", cdi->last_sense);
+ cdi->cdda_method = CDDA_OLD;
+ return cdrom_read_cdda_old(cdi, ubuf, lba, nframes);
+}
+
/* Just about every imaginable ioctl is supported in the Uniform layer
* these days. ATAPI / SCSI specific code now mainly resides in
* mmc_ioct().
@@ -2280,7 +2428,7 @@ static int mmc_ioctl(struct cdrom_device_info *cdi, unsigned int cmd,
}
case CDROMREADAUDIO: {
struct cdrom_read_audio ra;
- int lba, nr;
+ int lba;
IOCTL_IN(arg, struct cdrom_read_audio, ra);
@@ -2297,40 +2445,7 @@ static int mmc_ioctl(struct cdrom_device_info *cdi, unsigned int cmd,
if (lba < 0 || ra.nframes <= 0 || ra.nframes > CD_FRAMES)
return -EINVAL;
- /*
- * start with will ra.nframes size, back down if alloc fails
- */
- nr = ra.nframes;
- do {
- cgc.buffer = kmalloc(CD_FRAMESIZE_RAW * nr, GFP_KERNEL);
- if (cgc.buffer)
- break;
-
- nr >>= 1;
- } while (nr);
-
- if (!nr)
- return -ENOMEM;
-
- if (!access_ok(VERIFY_WRITE, ra.buf, ra.nframes*CD_FRAMESIZE_RAW)) {
- kfree(cgc.buffer);
- return -EFAULT;
- }
- cgc.data_direction = CGC_DATA_READ;
- while (ra.nframes > 0) {
- if (nr > ra.nframes)
- nr = ra.nframes;
-
- ret = cdrom_read_block(cdi, &cgc, lba, nr, 1, CD_FRAMESIZE_RAW);
- if (ret)
- break;
- __copy_to_user(ra.buf, cgc.buffer, CD_FRAMESIZE_RAW*nr);
- ra.buf += CD_FRAMESIZE_RAW * nr;
- ra.nframes -= nr;
- lba += nr;
- }
- kfree(cgc.buffer);
- return ret;
+ return cdrom_read_cdda(cdi, ra.buf, lba, ra.nframes);
}
case CDROMSUBCHNL: {
struct cdrom_subchnl q;
diff --git a/drivers/ide/ide-cd.c b/drivers/ide/ide-cd.c
index ac29198ad740..a1bdabd53740 100644
--- a/drivers/ide/ide-cd.c
+++ b/drivers/ide/ide-cd.c
@@ -2931,6 +2931,7 @@ static int ide_cdrom_register (ide_drive_t *drive, int nslots)
if (!CDROM_CONFIG_FLAGS(drive)->mrw_w)
devinfo->mask |= CDC_MRW_W;
+ devinfo->disk = drive->disk;
return register_cdrom(devinfo);
}
diff --git a/drivers/scsi/sr.c b/drivers/scsi/sr.c
index b2f1699fa0a5..809e42316326 100644
--- a/drivers/scsi/sr.c
+++ b/drivers/scsi/sr.c
@@ -566,10 +566,13 @@ static int sr_probe(struct device *dev)
snprintf(disk->devfs_name, sizeof(disk->devfs_name),
"%s/cd", sdev->devfs_name);
disk->driverfs_dev = &sdev->sdev_gendev;
- register_cdrom(&cd->cdi);
set_capacity(disk, cd->capacity);
disk->private_data = &cd->driver;
disk->queue = sdev->request_queue;
+ cd->cdi.disk = disk;
+
+ if (register_cdrom(&cd->cdi))
+ goto fail_put;
dev_set_drvdata(dev, cd);
add_disk(disk);
diff --git a/include/linux/cdrom.h b/include/linux/cdrom.h
index 513ac8f4e9a2..a4e851d4a335 100644
--- a/include/linux/cdrom.h
+++ b/include/linux/cdrom.h
@@ -877,10 +877,18 @@ struct mode_page_header {
#include <linux/fs.h> /* not really needed, later.. */
#include <linux/device.h>
+/*
+ * _OLD will use PIO transfer on atapi devices, _BPC_* will use DMA
+ */
+#define CDDA_OLD 0 /* old style */
+#define CDDA_BPC_SINGLE 1 /* single frame block pc */
+#define CDDA_BPC_FULL 2 /* multi frame block pc */
+
/* Uniform cdrom data structures for cdrom.c */
struct cdrom_device_info {
struct cdrom_device_ops *ops; /* link to device_ops */
struct cdrom_device_info *next; /* next device_info for this major */
+ struct gendisk *disk; /* matching block layer disk */
void *handle; /* driver-dependent data */
/* specifications */
int mask; /* mask of capability: disables them */
@@ -894,6 +902,8 @@ struct cdrom_device_info {
/* per-device flags */
__u8 sanyo_slot : 2; /* Sanyo 3 CD changer support */
__u8 reserved : 6; /* not used yet */
+ int cdda_method; /* see flags */
+ __u8 last_sense;
int for_data;
int (*exit)(struct cdrom_device_info *);
int mrw_mode_page;