diff options
| -rw-r--r-- | drivers/cdrom/cdrom.c | 185 | ||||
| -rw-r--r-- | drivers/ide/ide-cd.c | 1 | ||||
| -rw-r--r-- | drivers/scsi/sr.c | 5 | ||||
| -rw-r--r-- | include/linux/cdrom.h | 10 |
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; |
