Commit 62950936 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] mt rainier support

From: Jens Axboe <axboe@suse.de>

Following patch adds mt rainier support to the cdrom uniform layer (it
works with atapi and scsi/usb).
parent 9147d7be
......@@ -228,10 +228,16 @@
3.12 Oct 18, 2000 - Jens Axboe <axboe@suse.de>
-- Use quiet bit on packet commands not known to work
3.20 Dec 17, 2003 - Jens Axboe <axboe@suse.de>
-- Various fixes and lots of cleanups not listed :-)
-- Locking fixes
-- Mt Rainier support
-- DVD-RAM write open fixes
-------------------------------------------------------------------------*/
#define REVISION "Revision: 3.12"
#define VERSION "Id: cdrom.c 3.12 2000/10/18"
#define REVISION "Revision: 3.20"
#define VERSION "Id: cdrom.c 3.20 2003/12/17"
/* I use an error-log mask to give fine grain control over the type of
messages dumped to the system logs. The available masks include: */
......@@ -282,11 +288,25 @@ static int autoeject;
static int lockdoor = 1;
/* will we ever get to use this... sigh. */
static int check_media_type;
/* automatically restart mrw format */
static int mrw_format_restart = 1;
MODULE_PARM(debug, "i");
MODULE_PARM(autoclose, "i");
MODULE_PARM(autoeject, "i");
MODULE_PARM(lockdoor, "i");
MODULE_PARM(check_media_type, "i");
MODULE_PARM(mrw_format_restart, "i");
static spinlock_t cdrom_lock = SPIN_LOCK_UNLOCKED;
static const char *mrw_format_status[] = {
"not mrw",
"bgformat inactive",
"bgformat active",
"mrw complete",
};
static const char *mrw_address_space[] = { "DMA", "GAA" };
#if (ERRLOGMASK!=CD_NOTHING)
#define cdinfo(type, fmt, args...) \
......@@ -325,6 +345,10 @@ int cdrom_get_last_written(struct cdrom_device_info *, long *);
static int cdrom_get_next_writable(struct cdrom_device_info *, long *);
static void cdrom_count_tracks(struct cdrom_device_info *, tracktype*);
static int cdrom_mrw_exit(struct cdrom_device_info *cdi);
static int cdrom_get_disc_info(struct cdrom_device_info *cdi, disc_information *di);
#ifdef CONFIG_SYSCTL
static void cdrom_sysctl_register(void);
#endif /* CONFIG_SYSCTL */
......@@ -347,13 +371,14 @@ int register_cdrom(struct cdrom_device_info *cdi)
if (cdo->open == NULL || cdo->release == NULL)
return -2;
if ( !banner_printed ) {
if (!banner_printed) {
printk(KERN_INFO "Uniform CD-ROM driver " REVISION "\n");
banner_printed = 1;
#ifdef CONFIG_SYSCTL
cdrom_sysctl_register();
#endif /* CONFIG_SYSCTL */
}
ENSURE(drive_status, CDC_DRIVE_STATUS );
ENSURE(media_changed, CDC_MEDIA_CHANGED);
ENSURE(tray_move, CDC_CLOSE_TRAY | CDC_OPEN_TRAY);
......@@ -378,9 +403,14 @@ int register_cdrom(struct cdrom_device_info *cdi)
if (check_media_type==1)
cdi->options |= (int) CDO_CHECK_TYPE;
if (CDROM_CAN(CDC_MRW_W))
cdi->exit = cdrom_mrw_exit;
cdinfo(CD_REG_UNREG, "drive \"/dev/%s\" registered\n", cdi->name);
spin_lock(&cdrom_lock);
cdi->next = topCdromPtr;
topCdromPtr = cdi;
spin_unlock(&cdrom_lock);
return 0;
}
#undef ENSURE
......@@ -391,23 +421,305 @@ int unregister_cdrom(struct cdrom_device_info *unreg)
cdinfo(CD_OPEN, "entering unregister_cdrom\n");
prev = NULL;
spin_lock(&cdrom_lock);
cdi = topCdromPtr;
while (cdi && cdi != unreg) {
prev = cdi;
cdi = cdi->next;
}
if (cdi == NULL)
if (cdi == NULL) {
spin_unlock(&cdrom_lock);
return -2;
}
if (prev)
prev->next = cdi->next;
else
topCdromPtr = cdi->next;
spin_unlock(&cdrom_lock);
if (cdi->exit)
cdi->exit(cdi);
cdi->ops->n_minors--;
cdinfo(CD_REG_UNREG, "drive \"/dev/%s\" unregistered\n", cdi->name);
return 0;
}
int cdrom_get_media_event(struct cdrom_device_info *cdi,
struct media_event_desc *med)
{
struct cdrom_generic_command cgc;
unsigned char buffer[8];
struct event_header *eh = (struct event_header *) buffer;
init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ);
cgc.cmd[0] = GPCMD_GET_EVENT_STATUS_NOTIFICATION;
cgc.cmd[1] = 1; /* IMMED */
cgc.cmd[4] = 1 << 4; /* media event */
cgc.cmd[8] = sizeof(buffer);
cgc.quiet = 1;
if (cdi->ops->generic_packet(cdi, &cgc))
return 1;
if (be16_to_cpu(eh->data_len) < sizeof(*med))
return 1;
memcpy(med, &buffer[sizeof(*eh)], sizeof(*med));
return 0;
}
/*
* the first prototypes used 0x2c as the page code for the mrw mode page,
* subsequently this was changed to 0x03. probe the one used by this drive
*/
int cdrom_mrw_probe_pc(struct cdrom_device_info *cdi)
{
struct cdrom_generic_command cgc;
char buffer[16];
init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ);
cgc.timeout = HZ;
cgc.quiet = 1;
if (!cdrom_mode_sense(cdi, &cgc, MRW_MODE_PC, 0)) {
cdi->mrw_mode_page = MRW_MODE_PC;
return 0;
} else if (!cdrom_mode_sense(cdi, &cgc, MRW_MODE_PC_PRE1, 0)) {
cdi->mrw_mode_page = MRW_MODE_PC_PRE1;
return 0;
}
printk(KERN_ERR "cdrom: %s: unknown mrw mode page\n", cdi->name);
return 1;
}
int cdrom_is_mrw(struct cdrom_device_info *cdi, int *write)
{
struct cdrom_generic_command cgc;
struct mrw_feature_desc *mfd;
unsigned char buffer[16];
int ret;
init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ);
cgc.cmd[0] = GPCMD_GET_CONFIGURATION;
cgc.cmd[3] = CDF_MRW;
cgc.cmd[8] = sizeof(buffer);
cgc.quiet = 1;
if ((ret = cdi->ops->generic_packet(cdi, &cgc)))
return ret;
mfd = (struct mrw_feature_desc *)&buffer[sizeof(struct feature_header)];
*write = mfd->write;
if ((ret = cdrom_mrw_probe_pc(cdi)))
return ret;
return 0;
}
static int cdrom_mrw_bgformat(struct cdrom_device_info *cdi, int cont)
{
struct cdrom_generic_command cgc;
unsigned char buffer[12];
int ret;
printk(KERN_INFO "cdrom: %sstarting format\n", cont ? "Re" : "");
/*
* FmtData bit set (bit 4), format type is 1
*/
init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_WRITE);
cgc.cmd[0] = GPCMD_FORMAT_UNIT;
cgc.cmd[1] = (1 << 4) | 1;
cgc.timeout = 5 * 60 * HZ;
/*
* 4 byte format list header, 8 byte format list descriptor
*/
buffer[1] = 1 << 1;
buffer[3] = 8;
/*
* nr_blocks field
*/
buffer[4] = 0xff;
buffer[5] = 0xff;
buffer[6] = 0xff;
buffer[7] = 0xff;
buffer[8] = 0x24 << 2;
buffer[11] = cont;
ret = cdi->ops->generic_packet(cdi, &cgc);
if (ret)
printk(KERN_INFO "cdrom: bgformat failed\n");
return ret;
}
static int cdrom_mrw_bgformat_susp(struct cdrom_device_info *cdi, int immed)
{
struct cdrom_generic_command cgc;
init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
cgc.cmd[0] = GPCMD_CLOSE_TRACK;
/*
* Session = 1, Track = 0
*/
cgc.cmd[1] = !!immed;
cgc.cmd[2] = 1 << 1;
cgc.timeout = 5 * 60 * HZ;
return cdi->ops->generic_packet(cdi, &cgc);
}
static int cdrom_flush_cache(struct cdrom_device_info *cdi)
{
struct cdrom_generic_command cgc;
init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
cgc.cmd[0] = GPCMD_FLUSH_CACHE;
cgc.timeout = 5 * 60 * HZ;
return cdi->ops->generic_packet(cdi, &cgc);
}
static int cdrom_mrw_exit(struct cdrom_device_info *cdi)
{
disc_information di;
int ret = 0;
if (cdrom_get_disc_info(cdi, &di))
return 1;
if (di.mrw_status == CDM_MRW_BGFORMAT_ACTIVE) {
printk(KERN_INFO "cdrom: issuing MRW back ground format suspend\n");
ret = cdrom_mrw_bgformat_susp(cdi, 0);
}
if (!ret)
ret = cdrom_flush_cache(cdi);
return ret;
}
static int cdrom_mrw_set_lba_space(struct cdrom_device_info *cdi, int space)
{
struct cdrom_generic_command cgc;
struct mode_page_header *mph;
char buffer[16];
int ret, offset, size;
init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ);
cgc.buffer = buffer;
cgc.buflen = sizeof(buffer);
if ((ret = cdrom_mode_sense(cdi, &cgc, cdi->mrw_mode_page, 0)))
return ret;
mph = (struct mode_page_header *) buffer;
offset = be16_to_cpu(mph->desc_length);
size = be16_to_cpu(mph->mode_data_length) + 2;
buffer[offset + 3] = space;
cgc.buflen = size;
if ((ret = cdrom_mode_select(cdi, &cgc)))
return ret;
printk(KERN_INFO "cdrom: %s: mrw address space %s selected\n", cdi->name, mrw_address_space[space]);
return 0;
}
static int cdrom_media_erasable(struct cdrom_device_info *cdi)
{
disc_information di;
if (cdrom_get_disc_info(cdi, &di))
return 0;
return di.erasable;
}
/*
* FIXME: check RO bit
*/
static int cdrom_dvdram_open_write(struct cdrom_device_info *cdi)
{
return !cdrom_media_erasable(cdi);
}
static int cdrom_mrw_open_write(struct cdrom_device_info *cdi)
{
disc_information di;
int ret;
/*
* always reset to DMA lba space on open
*/
if (cdrom_mrw_set_lba_space(cdi, MRW_LBA_DMA)) {
printk(KERN_ERR "cdrom: failed setting lba address space\n");
return 1;
}
if (cdrom_get_disc_info(cdi, &di))
return 1;
if (!di.erasable)
return 1;
/*
* mrw_status
* 0 - not MRW formatted
* 1 - MRW bgformat started, but not running or complete
* 2 - MRW bgformat in progress
* 3 - MRW formatting complete
*/
ret = 0;
printk(KERN_INFO "cdrom open: mrw_status '%s'\n", mrw_format_status[di.mrw_status]);
if (!di.mrw_status)
ret = 1;
else if (di.mrw_status == CDM_MRW_BGFORMAT_INACTIVE && mrw_format_restart)
ret = cdrom_mrw_bgformat(cdi, 1);
return ret;
}
/*
* returns 0 for ok to open write, non-0 to disallow
*/
static int cdrom_open_write(struct cdrom_device_info *cdi)
{
int ret = 1;
if (CDROM_CAN(CDC_MRW_W))
ret = cdrom_mrw_open_write(cdi);
else if (CDROM_CAN(CDC_DVD_RAM))
ret = cdrom_dvdram_open_write(cdi);
return ret;
}
static int cdrom_close_write(struct cdrom_device_info *cdi)
{
#if 0
return cdrom_flush_cache(cdi);
#else
return 0;
#endif
}
/* We use the open-option O_NONBLOCK to indicate that the
* purpose of opening is only for subsequent ioctl() calls; no device
* integrity checks are performed.
......@@ -421,24 +733,29 @@ int cdrom_open(struct cdrom_device_info *cdi, struct inode *ip, struct file *fp)
int ret;
cdinfo(CD_OPEN, "entering cdrom_open\n");
ret = -EROFS;
if (fp->f_mode & FMODE_WRITE) {
if (!(CDROM_CAN(CDC_RAM) || CDROM_CAN(CDC_MO_DRIVE)))
goto out;
if (cdrom_open_write(cdi))
goto out;
}
/* if this was a O_NONBLOCK open and we should honor the flags,
* do a quick open without drive/disc integrity checks. */
if ((fp->f_flags & O_NONBLOCK) && (cdi->options & CDO_USE_FFLAGS))
ret = cdi->ops->open(cdi, 1);
else {
if ((fp->f_mode & FMODE_WRITE) &&
!(CDROM_CAN(CDC_DVD_RAM) || CDROM_CAN(CDC_MO_DRIVE)))
return -EROFS;
else
ret = open_for_data(cdi);
}
if (!ret) cdi->use_count++;
if (!ret)
cdi->use_count++;
cdinfo(CD_OPEN, "Use count for \"/dev/%s\" now %d\n", cdi->name, cdi->use_count);
/* Do this on open. Don't wait for mount, because they might
not be mounting, but opening with O_NONBLOCK */
check_disk_change(ip->i_bdev);
out:
return ret;
}
......@@ -526,7 +843,7 @@ int open_for_data(struct cdrom_device_info * cdi)
cdinfo(CD_OPEN, "open device failed.\n");
goto clean_up_and_return;
}
if (CDROM_CAN(CDC_LOCK) && cdi->options & CDO_LOCK) {
if (CDROM_CAN(CDC_LOCK) && (cdi->options & CDO_LOCK)) {
cdo->lock_door(cdi, 1);
cdinfo(CD_OPEN, "door locked.\n");
}
......@@ -604,7 +921,6 @@ int check_for_audio_disc(struct cdrom_device_info * cdi,
return 0;
}
/* Admittedly, the logic below could be performed in a nicer way. */
int cdrom_release(struct cdrom_device_info *cdi, struct file *fp)
{
......@@ -618,12 +934,19 @@ int cdrom_release(struct cdrom_device_info *cdi, struct file *fp)
if (cdi->use_count == 0)
cdinfo(CD_CLOSE, "Use count for \"/dev/%s\" now zero\n", cdi->name);
if (cdi->use_count == 0 &&
cdo->capability & CDC_LOCK && !keeplocked) {
(cdo->capability & CDC_LOCK) && !keeplocked) {
cdinfo(CD_CLOSE, "Unlocking door!\n");
cdo->lock_door(cdi, 0);
}
opened_for_data = !(cdi->options & CDO_USE_FFLAGS) ||
!(fp && fp->f_flags & O_NONBLOCK);
/*
* flush cache on last write release
*/
if (CDROM_CAN(CDC_RAM) && !cdi->use_count && cdi->for_data)
cdrom_close_write(cdi);
cdo->release(cdi);
if (cdi->use_count == 0) { /* last process that closes dev*/
if (opened_for_data &&
......@@ -2204,7 +2527,6 @@ static int cdrom_get_disc_info(struct cdrom_device_info *cdi, disc_information *
return cdo->generic_packet(cdi, &cgc);
}
/* return the last written block on the CD-R media. this is for the udf
file system. */
int cdrom_get_last_written(struct cdrom_device_info *cdi, long *last_written)
......@@ -2311,6 +2633,8 @@ EXPORT_SYMBOL(cdrom_number_of_slots);
EXPORT_SYMBOL(cdrom_mode_select);
EXPORT_SYMBOL(cdrom_mode_sense);
EXPORT_SYMBOL(init_cdrom_command);
EXPORT_SYMBOL(cdrom_get_media_event);
EXPORT_SYMBOL(cdrom_is_mrw);
#ifdef CONFIG_SYSCTL
......@@ -2407,6 +2731,14 @@ int cdrom_sysctl_info(ctl_table *ctl, int write, struct file * filp,
for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
pos += sprintf(info+pos, "\t%d", CDROM_CAN(CDC_DVD_RAM) != 0);
pos += sprintf(info+pos, "\nCan read MRW:");
for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
pos += sprintf(info+pos, "\t\t%d", CDROM_CAN(CDC_MRW) != 0);
pos += sprintf(info+pos, "\nCan write MRW:");
for (cdi=topCdromPtr;cdi!=NULL;cdi=cdi->next)
pos += sprintf(info+pos, "\t\t%d", CDROM_CAN(CDC_MRW_W) != 0);
strcpy(info+pos,"\n\n");
return proc_dostring(ctl, write, filp, buffer, lenp);
......
......@@ -291,10 +291,13 @@
* - Use extended sense on drives that support it for
* correctly reporting tray status -- from
* Michael D Johnson <johnsom@orst.edu>
* 4.60 Dec 17, 2003 - Add mt rainier support
* - Bump timeout for packet commands, matches sr
* - Odd stuff
*
*************************************************************************/
#define IDECD_VERSION "4.59-ac1"
#define IDECD_VERSION "4.60"
#include <linux/config.h>
#include <linux/module.h>
......@@ -774,11 +777,36 @@ static int cdrom_decode_status(ide_drive_t *drive, int good_stat, int *stat_ret)
if (sense_key == NOT_READY) {
/* Tray open. */
if (rq_data_dir(rq) == READ) {
cdrom_saw_media_change (drive);
/* Fail the request. */
printk ("%s: tray open\n", drive->name);
do_end_request = 1;
} else {
struct cdrom_info *info = drive->driver_data;
/* allow the drive 5 seconds to recover, some
* devices will return this error while flushing
* data from cache */
if (!rq->errors)
info->write_timeout = jiffies + ATAPI_WAIT_BUSY;
rq->errors = 1;
if (time_after(jiffies, info->write_timeout))
do_end_request = 1;
else {
unsigned long flags;
/*
* take a breather relying on the
* unplug timer to kick us again
*/
spin_lock_irqsave(&ide_lock, flags);
blk_plug_device(drive->queue);
spin_unlock_irqrestore(&ide_lock,flags);
return 1;
}
}
} else if (sense_key == UNIT_ATTENTION) {
/* Media change. */
cdrom_saw_media_change (drive);
......@@ -844,9 +872,13 @@ static int cdrom_timer_expiry(ide_drive_t *drive)
case GPCMD_BLANK:
case GPCMD_FORMAT_UNIT:
case GPCMD_RESERVE_RZONE_TRACK:
wait = WAIT_CMD;
case GPCMD_CLOSE_TRACK:
case GPCMD_FLUSH_CACHE:
wait = ATAPI_WAIT_PC;
break;
default:
if (!(rq->flags & REQ_QUIET))
printk(KERN_INFO "ide-cd: cmd 0x%x timed out\n", rq->cmd[0]);
wait = 0;
break;
}
......@@ -894,7 +926,7 @@ static ide_startstop_t cdrom_start_packet_command(ide_drive_t *drive,
if (CDROM_CONFIG_FLAGS (drive)->drq_interrupt) {
/* packet command */
ide_execute_command(drive, WIN_PACKETCMD, handler, WAIT_CMD, cdrom_timer_expiry);
ide_execute_command(drive, WIN_PACKETCMD, handler, ATAPI_WAIT_PC, cdrom_timer_expiry);
return ide_started;
} else {
/* packet command */
......@@ -1167,7 +1199,7 @@ static ide_startstop_t cdrom_read_intr (ide_drive_t *drive)
}
/* Done moving data! Wait for another interrupt. */
ide_set_handler(drive, &cdrom_read_intr, WAIT_CMD, NULL);
ide_set_handler(drive, &cdrom_read_intr, ATAPI_WAIT_PC, NULL);
return ide_started;
}
......@@ -1277,7 +1309,7 @@ static ide_startstop_t cdrom_start_read_continuation (ide_drive_t *drive)
(65534 / CD_FRAMESIZE) : 65535);
/* Set up the command */
rq->timeout = WAIT_CMD;
rq->timeout = ATAPI_WAIT_PC;
/* Send the command to the drive and return. */
return cdrom_transfer_packet_command(drive, rq, &cdrom_read_intr);
......@@ -1286,7 +1318,7 @@ static ide_startstop_t cdrom_start_read_continuation (ide_drive_t *drive)
#define IDECD_SEEK_THRESHOLD (1000) /* 1000 blocks */
#define IDECD_SEEK_TIMER (5 * WAIT_MIN_SLEEP) /* 100 ms */
#define IDECD_SEEK_TIMEOUT WAIT_CMD /* 10 sec */
#define IDECD_SEEK_TIMEOUT (2 * WAIT_CMD) /* 20 sec */
static ide_startstop_t cdrom_seek_intr (ide_drive_t *drive)
{
......@@ -1326,7 +1358,7 @@ static ide_startstop_t cdrom_start_seek_continuation (ide_drive_t *drive)
rq->cmd[0] = GPCMD_SEEK;
put_unaligned(cpu_to_be32(frame), (unsigned int *) &rq->cmd[2]);
rq->timeout = WAIT_CMD;
rq->timeout = ATAPI_WAIT_PC;
return cdrom_transfer_packet_command(drive, rq, &cdrom_seek_intr);
}
......@@ -1502,7 +1534,7 @@ static ide_startstop_t cdrom_pc_intr (ide_drive_t *drive)
}
/* Now we wait for another interrupt. */
ide_set_handler(drive, &cdrom_pc_intr, WAIT_CMD, cdrom_timer_expiry);
ide_set_handler(drive, &cdrom_pc_intr, ATAPI_WAIT_PC, cdrom_timer_expiry);
return ide_started;
}
......@@ -1511,7 +1543,7 @@ static ide_startstop_t cdrom_do_pc_continuation (ide_drive_t *drive)
struct request *rq = HWGROUP(drive)->rq;
if (!rq->timeout)
rq->timeout = WAIT_CMD;
rq->timeout = ATAPI_WAIT_PC;
/* Send the command to the drive and return. */
return cdrom_transfer_packet_command(drive, rq, &cdrom_pc_intr);
......@@ -1716,11 +1748,8 @@ static ide_startstop_t cdrom_newpc_intr(ide_drive_t *drive)
/*
* If DRQ is clear, the command has completed.
*/
if ((stat & DRQ_STAT) == 0) {
if (rq->data_len)
printk("%s: %u residual after xfer\n", __FUNCTION__, rq->data_len);
if ((stat & DRQ_STAT) == 0)
goto end_request;
}
/*
* check which way to transfer data
......@@ -1826,10 +1855,8 @@ static ide_startstop_t cdrom_write_intr(ide_drive_t *drive)
}
}
if (cdrom_decode_status(drive, 0, &stat)) {
printk("ide-cd: write_intr decode_status bad\n");
if (cdrom_decode_status(drive, 0, &stat))
return ide_stopped;
}
/*
* using dma, transfer is complete now
......@@ -1904,7 +1931,7 @@ static ide_startstop_t cdrom_write_intr(ide_drive_t *drive)
}
/* re-arm handler */
ide_set_handler(drive, &cdrom_write_intr, 5 * WAIT_CMD, NULL);
ide_set_handler(drive, &cdrom_write_intr, ATAPI_WAIT_PC, NULL);
return ide_started;
}
......@@ -1915,7 +1942,7 @@ static ide_startstop_t cdrom_start_write_cont(ide_drive_t *drive)
#if 0 /* the immediate bit */
rq->cmd[1] = 1 << 3;
#endif
rq->timeout = 2 * WAIT_CMD;
rq->timeout = ATAPI_WAIT_PC;
return cdrom_transfer_packet_command(drive, rq, cdrom_write_intr);
}
......@@ -1956,7 +1983,7 @@ static ide_startstop_t cdrom_do_newpc_cont(ide_drive_t *drive)
struct request *rq = HWGROUP(drive)->rq;
if (!rq->timeout)
rq->timeout = WAIT_CMD;
rq->timeout = ATAPI_WAIT_PC;
return cdrom_transfer_packet_command(drive, rq, cdrom_newpc_intr);
}
......@@ -2489,7 +2516,7 @@ static int ide_cdrom_packet(struct cdrom_device_info *cdi,
ide_drive_t *drive = (ide_drive_t*) cdi->handle;
if (cgc->timeout <= 0)
cgc->timeout = WAIT_CMD;
cgc->timeout = ATAPI_WAIT_PC;
/* here we queue the commands from the uniform CD-ROM
layer. the packet must be complete, as we do not
......@@ -2694,21 +2721,35 @@ int ide_cdrom_select_speed (struct cdrom_device_info *cdi, int speed)
return 0;
}
/*
* add logic to try GET_EVENT command first to check for media and tray
* status. this should be supported by newer cd-r/w and all DVD etc
* drives
*/
static
int ide_cdrom_drive_status (struct cdrom_device_info *cdi, int slot_nr)
{
ide_drive_t *drive = (ide_drive_t*) cdi->handle;
if (slot_nr == CDSL_CURRENT) {
struct media_event_desc med;
struct request_sense sense;
int stat = cdrom_check_status(drive, &sense);
if (stat == 0 || sense.sense_key == UNIT_ATTENTION)
int stat;
if (slot_nr != CDSL_CURRENT)
return -EINVAL;
stat = cdrom_check_status(drive, &sense);
if (!stat || sense.sense_key == UNIT_ATTENTION)
return CDS_DISC_OK;
if (sense.sense_key == NOT_READY && sense.asc == 0x04 &&
sense.ascq == 0x04)
if (!cdrom_get_media_event(cdi, &med)) {
if (med.media_present)
return CDS_DISC_OK;
if (med.door_open)
return CDS_TRAY_OPEN;
}
if (sense.sense_key == NOT_READY && sense.asc == 0x04 && sense.ascq == 0x04)
return CDS_DISC_OK;
/*
* If not using Mt Fuji extended media tray reports,
......@@ -2723,8 +2764,6 @@ int ide_cdrom_drive_status (struct cdrom_device_info *cdi, int slot_nr)
}
return CDS_DRIVE_NOT_READY;
}
return -EINVAL;
}
static
......@@ -2832,7 +2871,8 @@ static struct cdrom_device_ops ide_cdrom_dops = {
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_MO_DRIVE,
CDC_GENERIC_PACKET | CDC_MO_DRIVE | CDC_MRW |
CDC_MRW_W | CDC_RAM,
.generic_packet = ide_cdrom_packet,
};
......@@ -2867,6 +2907,10 @@ static int ide_cdrom_register (ide_drive_t *drive, int nslots)
devinfo->mask |= CDC_CLOSE_TRAY;
if (!CDROM_CONFIG_FLAGS(drive)->mo_drive)
devinfo->mask |= CDC_MO_DRIVE;
if (!CDROM_CONFIG_FLAGS(drive)->mrw)
devinfo->mask |= CDC_MRW;
if (!CDROM_CONFIG_FLAGS(drive)->mrw_w)
devinfo->mask |= CDC_MRW_W;
return register_cdrom(devinfo);
}
......@@ -2887,14 +2931,6 @@ int ide_cdrom_get_capabilities(ide_drive_t *drive, struct atapi_capabilities_pag
!strcmp(drive->id->model, "WPI CDS-32X")))
size -= sizeof(cap->pad);
/* we have to cheat a little here. the packet will eventually
* be queued with ide_cdrom_packet(), which extracts the
* drive from cdi->handle. Since this device hasn't been
* registered with the Uniform layer yet, it can't do this.
* Same goes for cdi->ops.
*/
cdi->handle = (ide_drive_t *) drive;
cdi->ops = &ide_cdrom_dops;
init_cdrom_command(&cgc, cap, size, CGC_DATA_UNKNOWN);
do { /* we seem to get stat=0x01,err=0x00 the first time (??) */
stat = cdrom_mode_sense(cdi, &cgc, GPMODE_CAPABILITIES_PAGE, 0);
......@@ -2910,7 +2946,7 @@ int ide_cdrom_probe_capabilities (ide_drive_t *drive)
struct cdrom_info *info = drive->driver_data;
struct cdrom_device_info *cdi = &info->devinfo;
struct atapi_capabilities_page cap;
int nslots = 1;
int nslots = 1, mrw_write = 0;
if (drive->media == ide_optical) {
CDROM_CONFIG_FLAGS(drive)->mo_drive = 1;
......@@ -2918,15 +2954,34 @@ int ide_cdrom_probe_capabilities (ide_drive_t *drive)
return nslots;
}
if (CDROM_CONFIG_FLAGS(drive)->nec260) {
if (CDROM_CONFIG_FLAGS(drive)->nec260 ||
!strcmp(drive->id->model,"STINGRAY 8422 IDE 8X CD-ROM 7-27-95")) {
CDROM_CONFIG_FLAGS(drive)->no_eject = 0;
CDROM_CONFIG_FLAGS(drive)->audio_play = 1;
return nslots;
}
/*
* we have to cheat a little here. the packet will eventually
* be queued with ide_cdrom_packet(), which extracts the
* drive from cdi->handle. Since this device hasn't been
* registered with the Uniform layer yet, it can't do this.
* Same goes for cdi->ops.
*/
cdi->handle = (ide_drive_t *) drive;
cdi->ops = &ide_cdrom_dops;
if (ide_cdrom_get_capabilities(drive, &cap))
return 0;
if (!cdrom_is_mrw(cdi, &mrw_write)) {
CDROM_CONFIG_FLAGS(drive)->mrw = 1;
if (mrw_write) {
CDROM_CONFIG_FLAGS(drive)->mrw_w = 1;
CDROM_CONFIG_FLAGS(drive)->ram = 1;
}
}
if (cap.lock == 0)
CDROM_CONFIG_FLAGS(drive)->no_doorlock = 1;
if (cap.eject)
......@@ -2939,8 +2994,10 @@ int ide_cdrom_probe_capabilities (ide_drive_t *drive)
CDROM_CONFIG_FLAGS(drive)->test_write = 1;
if (cap.dvd_ram_read || cap.dvd_r_read || cap.dvd_rom)
CDROM_CONFIG_FLAGS(drive)->dvd = 1;
if (cap.dvd_ram_write)
if (cap.dvd_ram_write) {
CDROM_CONFIG_FLAGS(drive)->dvd_ram = 1;
CDROM_CONFIG_FLAGS(drive)->ram = 1;
}
if (cap.dvd_r_write)
CDROM_CONFIG_FLAGS(drive)->dvd_r = 1;
if (cap.audio_play)
......@@ -3004,6 +3061,9 @@ int ide_cdrom_probe_capabilities (ide_drive_t *drive)
(CDROM_CONFIG_FLAGS(drive)->cd_r)? "-R" : "",
(CDROM_CONFIG_FLAGS(drive)->cd_rw)? "/RW" : "");
if (CDROM_CONFIG_FLAGS(drive)->mrw || CDROM_CONFIG_FLAGS(drive)->mrw_w)
printk(" CD-MR%s", CDROM_CONFIG_FLAGS(drive)->mrw_w ? "W" : "");
if (CDROM_CONFIG_FLAGS(drive)->is_changer)
printk(" changer w/%d slots", nslots);
else
......@@ -3111,14 +3171,11 @@ int ide_cdrom_setup (ide_drive_t *drive)
struct cdrom_device_info *cdi = &info->devinfo;
int nslots;
/*
* default to read-only always and fix latter at the bottom
*/
set_disk_ro(drive->disk, 1);
blk_queue_hardsect_size(drive->queue, CD_FRAMESIZE);
blk_queue_prep_rq(drive->queue, ide_cdrom_prep_fn);
blk_queue_dma_alignment(drive->queue, 3);
drive->queue->unplug_delay = (1 * HZ) / 1000;
if (!drive->queue->unplug_delay)
drive->queue->unplug_delay = 1;
drive->special.all = 0;
drive->ready_stat = 0;
......@@ -3221,9 +3278,13 @@ int ide_cdrom_setup (ide_drive_t *drive)
nslots = ide_cdrom_probe_capabilities (drive);
if (CDROM_CONFIG_FLAGS(drive)->dvd_ram ||
CDROM_CONFIG_FLAGS(drive)->mo_drive)
set_disk_ro(drive->disk, 0);
/*
* set correct block size and read-only for non-ram media
*/
set_disk_ro(drive->disk,
!(CDROM_CONFIG_FLAGS(drive)->ram ||
CDROM_CONFIG_FLAGS(drive)->mo_drive));
blk_queue_hardsect_size(drive->queue, CD_FRAMESIZE);
#if 0
drive->dsc_overlap = (HWIF(drive)->no_dsc) ? 0 : 1;
......
......@@ -35,6 +35,12 @@
#define NO_DOOR_LOCKING 0
#endif
/*
* typical timeout for packet command
*/
#define ATAPI_WAIT_PC (60 * HZ)
#define ATAPI_WAIT_BUSY (5 * HZ)
/************************************************************************/
#define SECTOR_BITS 9
......@@ -75,6 +81,9 @@ struct ide_cd_config_flags {
__u8 dvd : 1; /* Drive is a DVD-ROM */
__u8 dvd_r : 1; /* Drive can write DVD-R */
__u8 dvd_ram : 1; /* Drive can write DVD-RAM */
__u8 mrw : 1; /* drive can read mrw */
__u8 mrw_w : 1; /* drive can write mrw */
__u8 ram : 1; /* generic WRITE (dvd-ram/mrw) */
__u8 test_write : 1; /* Drive can fake writes */
__u8 supp_disc_present : 1; /* Changer can report exact contents
of slots. */
......@@ -482,6 +491,8 @@ struct cdrom_info {
/* Per-device info needed by cdrom.c generic driver. */
struct cdrom_device_info devinfo;
unsigned long write_timeout;
};
/****************************************************************************
......
......@@ -67,7 +67,8 @@ MODULE_PARM(xa_test, "i"); /* see sr_ioctl.c */
(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_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 *);
......@@ -692,7 +693,7 @@ static void get_sectorsize(struct scsi_cd *cd)
static void get_capabilities(struct scsi_cd *cd)
{
unsigned char *buffer;
int rc, n;
int rc, n, mrw_write = 0, mrw = 1;
struct scsi_mode_data data;
struct scsi_request *SRpnt;
unsigned char cmd[MAX_COMMAND_SIZE];
......@@ -765,6 +766,15 @@ static void get_capabilities(struct scsi_cd *cd)
printk("%s: scsi-1 drive\n", cd->cdi.name);
return;
}
if (cdrom_is_mrw(&cd->cdi, &mrw_write)) {
mrw = 0;
cd->cdi.mask |= CDC_MRW;
cd->cdi.mask |= CDC_MRW_W;
}
if (!mrw_write)
cd->cdi.mask |= CDC_MRW_W;
n = data.header_length + data.block_descriptor_length;
cd->cdi.speed = ((buffer[n + 8] << 8) + buffer[n + 9]) / 176;
cd->readcd_known = 1;
......@@ -788,9 +798,7 @@ static void get_capabilities(struct scsi_cd *cd)
if ((buffer[n + 3] & 0x20) == 0) {
/* can't write DVD-RAM media */
cd->cdi.mask |= CDC_DVD_RAM;
} else {
cd->device->writeable = 1;
}
} else
if ((buffer[n + 3] & 0x10) == 0)
/* can't write DVD-R media */
cd->cdi.mask |= CDC_DVD_R;
......@@ -814,6 +822,12 @@ static void get_capabilities(struct scsi_cd *cd)
/*else I don't think it can close its tray
cd->cdi.mask |= CDC_CLOSE_TRAY; */
/*
* if DVD-RAM of MRW-W, we are randomly writeable
*/
if ((cd->cdi.mask & (CDC_DVD_RAM | CDC_MRW_W)) != (CDC_DVD_RAM | CDC_MRW_W))
cd->device->writeable = 1;
scsi_release_request(SRpnt);
kfree(buffer);
}
......
......@@ -5,7 +5,7 @@
* 1994, 1995 Eberhard Moenkeberg, emoenke@gwdg.de
* 1996 David van Leeuwen, david@tm.tno.nl
* 1997, 1998 Erik Andersen, andersee@debian.org
* 1998-2000 Jens Axboe, axboe@suse.de
* 1998-2002 Jens Axboe, axboe@suse.de
*/
#ifndef _LINUX_CDROM_H
......@@ -388,6 +388,9 @@ struct cdrom_generic_command
#define CDC_DVD_R 0x10000 /* drive can write DVD-R */
#define CDC_DVD_RAM 0x20000 /* drive can write DVD-RAM */
#define CDC_MO_DRIVE 0x40000 /* drive is an MO device */
#define CDC_MRW 0x80000 /* drive can read MRW */
#define CDC_MRW_W 0x100000 /* drive can write MRW */
#define CDC_RAM 0x200000 /* ok to open for WRITE */
/* drive status possibilities returned by CDROM_DRIVE_STATUS ioctl */
#define CDS_NO_INFO 0 /* if not implemented */
......@@ -715,92 +718,57 @@ struct request_sense {
__u8 asb[46];
};
#ifdef __KERNEL__
#include <linux/fs.h> /* not really needed, later.. */
#include <linux/device.h>
struct cdrom_write_settings {
unsigned char fpacket; /* fixed/variable packets */
unsigned long packet_size; /* write out this number of packets */
unsigned long nwa; /* next writeable address */
unsigned char writeable; /* cdrom is writeable */
};
/* 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 */
void *handle; /* driver-dependent data */
/* specifications */
int mask; /* mask of capability: disables them */
int speed; /* maximum speed for reading data */
int capacity; /* number of discs in jukebox */
/* device-related storage */
int options : 30; /* options flags */
unsigned mc_flags : 2; /* media change buffer flags */
int use_count; /* number of times device opened */
char name[20]; /* name of the device type */
/* per-device flags */
__u8 sanyo_slot : 2; /* Sanyo 3 CD changer support */
__u8 reserved : 6; /* not used yet */
struct cdrom_write_settings write;
};
struct cdrom_device_ops {
/* routines */
int (*open) (struct cdrom_device_info *, int);
void (*release) (struct cdrom_device_info *);
int (*drive_status) (struct cdrom_device_info *, int);
int (*media_changed) (struct cdrom_device_info *, int);
int (*tray_move) (struct cdrom_device_info *, int);
int (*lock_door) (struct cdrom_device_info *, int);
int (*select_speed) (struct cdrom_device_info *, int);
int (*select_disc) (struct cdrom_device_info *, int);
int (*get_last_session) (struct cdrom_device_info *,
struct cdrom_multisession *);
int (*get_mcn) (struct cdrom_device_info *,
struct cdrom_mcn *);
/* hard reset device */
int (*reset) (struct cdrom_device_info *);
/* play stuff */
int (*audio_ioctl) (struct cdrom_device_info *,unsigned int, void *);
/* dev-specific */
int (*dev_ioctl) (struct cdrom_device_info *,
unsigned int, unsigned long);
/* driver specifications */
const int capability; /* capability flags */
int n_minors; /* number of active minor devices */
/* handle uniform packets for scsi type devices (scsi,atapi) */
int (*generic_packet) (struct cdrom_device_info *,
struct cdrom_generic_command *);
};
/*
* feature profile
*/
#define CDF_MRW 0x28
/* the general block_device operations structure: */
extern int cdrom_open(struct cdrom_device_info *, struct inode *, struct file *);
extern int cdrom_release(struct cdrom_device_info *, struct file *);
extern int cdrom_ioctl(struct cdrom_device_info *, struct inode *, unsigned, unsigned long);
extern int cdrom_media_changed(struct cdrom_device_info *);
/*
* media status bits
*/
#define CDM_MRW_NOTMRW 0
#define CDM_MRW_BGFORMAT_INACTIVE 1
#define CDM_MRW_BGFORMAT_ACTIVE 2
#define CDM_MRW_BGFORMAT_COMPLETE 3
extern int register_cdrom(struct cdrom_device_info *cdi);
extern int unregister_cdrom(struct cdrom_device_info *cdi);
/*
* mrw address spaces
*/
#define MRW_LBA_DMA 0
#define MRW_LBA_GAA 1
typedef struct {
int data;
int audio;
int cdi;
int xa;
long error;
} tracktype;
/*
* mrw mode pages (first is deprecated) -- probed at init time and
* cdi->mrw_mode_page is set
*/
#define MRW_MODE_PC_PRE1 0x2c
#define MRW_MODE_PC 0x03
extern int cdrom_get_last_written(struct cdrom_device_info *cdi, long *last_written);
extern int cdrom_number_of_slots(struct cdrom_device_info *cdi);
extern int cdrom_mode_select(struct cdrom_device_info *cdi,
struct cdrom_generic_command *cgc);
extern int cdrom_mode_sense(struct cdrom_device_info *cdi,
struct cdrom_generic_command *cgc,
int page_code, int page_control);
extern void init_cdrom_command(struct cdrom_generic_command *cgc,
void *buffer, int len, int type);
struct mrw_feature_desc {
__u16 feature_code;
#if defined(__BIG_ENDIAN_BITFIELD)
__u8 reserved1 : 2;
__u8 feature_version : 4;
__u8 persistent : 1;
__u8 curr : 1;
#elif defined(__LITTLE_ENDIAN_BITFIELD)
__u8 curr : 1;
__u8 persistent : 1;
__u8 feature_version : 4;
__u8 reserved1 : 2;
#endif
__u8 add_len;
#if defined(__BIG_ENDIAN_BITFIELD)
__u8 reserved2 : 7;
__u8 write : 1;
#elif defined(__LITTLE_ENDIAN_BITFIELD)
__u8 write : 1;
__u8 reserved2 : 7;
#endif
__u8 reserved3;
__u8 reserved4;
__u8 reserved5;
};
typedef struct {
__u16 disc_information_length;
......@@ -825,9 +793,13 @@ typedef struct {
__u8 did_v : 1;
__u8 dbc_v : 1;
__u8 uru : 1;
__u8 reserved2 : 5;
__u8 reserved2 : 2;
__u8 dbit : 1;
__u8 mrw_status : 2;
#elif defined(__LITTLE_ENDIAN_BITFIELD)
__u8 reserved2 : 5;
__u8 mrw_status : 2;
__u8 dbit : 1;
__u8 reserved2 : 2;
__u8 uru : 1;
__u8 dbc_v : 1;
__u8 did_v : 1;
......@@ -884,6 +856,106 @@ typedef struct {
__u32 last_rec_address;
} track_information;
struct feature_header {
__u32 data_len;
__u8 reserved1;
__u8 reserved2;
__u16 curr_profile;
};
struct mode_page_header {
__u16 mode_data_length;
__u8 medium_type;
__u8 reserved1;
__u8 reserved2;
__u8 reserved3;
__u16 desc_length;
};
#ifdef __KERNEL__
#include <linux/fs.h> /* not really needed, later.. */
#include <linux/device.h>
/* 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 */
void *handle; /* driver-dependent data */
/* specifications */
int mask; /* mask of capability: disables them */
int speed; /* maximum speed for reading data */
int capacity; /* number of discs in jukebox */
/* device-related storage */
int options : 30; /* options flags */
unsigned mc_flags : 2; /* media change buffer flags */
int use_count; /* number of times device opened */
char name[20]; /* name of the device type */
/* per-device flags */
__u8 sanyo_slot : 2; /* Sanyo 3 CD changer support */
__u8 reserved : 6; /* not used yet */
int for_data;
int (*exit)(struct cdrom_device_info *);
int mrw_mode_page;
};
struct cdrom_device_ops {
/* routines */
int (*open) (struct cdrom_device_info *, int);
void (*release) (struct cdrom_device_info *);
int (*drive_status) (struct cdrom_device_info *, int);
int (*media_changed) (struct cdrom_device_info *, int);
int (*tray_move) (struct cdrom_device_info *, int);
int (*lock_door) (struct cdrom_device_info *, int);
int (*select_speed) (struct cdrom_device_info *, int);
int (*select_disc) (struct cdrom_device_info *, int);
int (*get_last_session) (struct cdrom_device_info *,
struct cdrom_multisession *);
int (*get_mcn) (struct cdrom_device_info *,
struct cdrom_mcn *);
/* hard reset device */
int (*reset) (struct cdrom_device_info *);
/* play stuff */
int (*audio_ioctl) (struct cdrom_device_info *,unsigned int, void *);
/* dev-specific */
int (*dev_ioctl) (struct cdrom_device_info *,
unsigned int, unsigned long);
/* driver specifications */
const int capability; /* capability flags */
int n_minors; /* number of active minor devices */
/* handle uniform packets for scsi type devices (scsi,atapi) */
int (*generic_packet) (struct cdrom_device_info *,
struct cdrom_generic_command *);
};
/* the general block_device operations structure: */
extern int cdrom_open(struct cdrom_device_info *cdi, struct inode *ip,
struct file *fp);
extern int cdrom_release(struct cdrom_device_info *cdi, struct file *fp);
extern int cdrom_ioctl(struct cdrom_device_info *cdi, struct inode *ip,
unsigned int cmd, unsigned long arg);
extern int cdrom_media_changed(struct cdrom_device_info *);
extern int register_cdrom(struct cdrom_device_info *cdi);
extern int unregister_cdrom(struct cdrom_device_info *cdi);
typedef struct {
int data;
int audio;
int cdi;
int xa;
long error;
} tracktype;
extern int cdrom_get_last_written(struct cdrom_device_info *cdi, long *last_written);
extern int cdrom_number_of_slots(struct cdrom_device_info *cdi);
extern int cdrom_mode_select(struct cdrom_device_info *cdi,
struct cdrom_generic_command *cgc);
extern int cdrom_mode_sense(struct cdrom_device_info *cdi,
struct cdrom_generic_command *cgc,
int page_code, int page_control);
extern void init_cdrom_command(struct cdrom_generic_command *cgc,
void *buffer, int len, int type);
/* The SCSI spec says there could be 256 slots. */
#define CDROM_MAX_SLOTS 256
......@@ -934,15 +1006,6 @@ typedef enum {
mechtype_cartridge_changer = 5
} mechtype_t;
struct mode_page_header {
__u16 mode_data_length;
__u8 medium_type;
__u8 reserved1;
__u8 reserved2;
__u8 reserved3;
__u16 desc_length;
};
typedef struct {
#if defined(__BIG_ENDIAN_BITFIELD)
__u8 ps : 1;
......@@ -1032,6 +1095,41 @@ typedef struct {
__u8 reserved3;
} rpc_state_t;
struct event_header {
__u16 data_len;
#if defined(__BIG_ENDIAN_BITFIELD)
__u8 nea : 1;
__u8 reserved1 : 4;
__u8 notification_class : 3;
#elif defined(__LITTLE_ENDIAN_BITFIELD)
__u8 notification_class : 3;
__u8 reserved1 : 4;
__u8 nea : 1;
#endif
__u8 supp_event_class;
};
struct media_event_desc {
#if defined(__BIG_ENDIAN_BITFIELD)
__u8 reserved1 : 4;
__u8 media_event_code : 4;
__u8 reserved2 : 6;
__u8 media_present : 1;
__u8 door_open : 1;
#elif defined(__LITTLE_ENDIAN_BITFIELD)
__u8 media_event_code : 4;
__u8 reserved1 : 4;
__u8 door_open : 1;
__u8 media_present : 1;
__u8 reserved2 : 6;
#endif
__u8 start_slot;
__u8 end_slot;
};
extern int cdrom_get_media_event(struct cdrom_device_info *cdi, struct media_event_desc *med);
extern int cdrom_is_mrw(struct cdrom_device_info *cdi, int *write);
#endif /* End of kernel only stuff */
#endif /* _LINUX_CDROM_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment