The following is my first attempt at kernel hacking, since the kernel has always done everything I wanted. After I bought an NEC CDR-C251, 4 CD changer, I realized the kernel support for ATAPI CD changers somewhat weak. The following patches update drivers/block/ide-cd.c, include/linux/cdrom.h, and Documentation/cdrom/ide-cd to increase reliability by adding additional error checking, bring the VERBOSE_IDE_CD_ERRORS up to date per the ATAPI 2.6 draft standard ( available via anonymous ftp from ftp://fission.dt.wdc.com/pub/standards/atapi/ -- essential reading!!! ), and I added two new ioctl calls, CDROMMECHANISMSTATUS, and CDROMREADSLOTTABLE. These allow the user much greater control, should be safe for any program to check if a CD player is a changer, and will allow somebody to update the current CD player programs to utilize cd changer functions if they exist on a device. I have tried these out on my machine using the (adjusted) sample code in Documentation/cdrom/ide-cd, and have found it to work VERY WELL. :-) I think it is stable enough for 2.0.13, but please (anybody), if you have an ATAPI cd changer (even the Sanyo 3-cd one, which I believe this won't break or help), try this out and see if it works for you. Scott Snyder , let me know what you think. Is this worthy of inclusion in the kernel? -Erik Erik B. Andersen 2485 South State St. email: andersee@et.byu.edu Springville, Ut 84663 phone: (801) 489-1231 --This message was written using 73% post-consumer electrons-- Lots of patches follow: diff -u --recursive --new-file /home/andersee/ide-cd.c linux/drivers/block/ide-cd.c --- /home/andersee/ide-cd.c Mon Aug 12 11:18:26 1996 +++ linux/drivers/block/ide-cd.c Tue Aug 13 23:24:49 1996 @@ -194,6 +193,7 @@ #define READ_CD 0xbe #define LOAD_UNLOAD 0xa6 +#define MECHANISM_STATUS 0xbd /* ATAPI sense keys (mostly copied from scsi.h). */ @@ -291,7 +291,8 @@ #if VERBOSE_IDE_CD_ERRORS -/* From Table 124 of the ATAPI 1.2 spec. */ +/* From Table 124 of the ATAPI 1.2 spec. + Unchanged in Table 140 of the ATAPI 2.6 draft standard. */ char *sense_key_texts[16] = { "No sense data", @@ -313,29 +314,43 @@ }; -/* From Table 125 of the ATAPI 1.2 spec. */ +/* From Table 125 of the ATAPI 1.2 spec. + with additions from Table 141 and Table 142 + of the ATAPI 2.6 draft standard. */ struct { short asc_ascq; char *text; } sense_data_texts[] = { { 0x0000, "No additional sense information" }, + { 0x0011, "Audio play operation in progress" }, { 0x0012, "Audio play operation paused" }, { 0x0013, "Audio play operation successfully completed" }, { 0x0014, "Audio play operation stopped due to error" }, { 0x0015, "No current audio status to return" }, + { 0x0100, "Mechanical positioning or changer error" }, + { 0x0200, "No seek complete" }, { 0x0400, "Logical unit not ready - cause not reportable" }, - { 0x0401, - "Logical unit not ready - in progress (sic) of becoming ready" }, + { 0x0401, "Logical unit not ready - in progress (sic) of becoming ready" }, { 0x0402, "Logical unit not ready - initializing command required" }, { 0x0403, "Logical unit not ready - manual intervention required" }, + { 0x0501, "Media load - eject failed" }, + + { 0x051a, "Parameter list length error" }, + { 0x0520, "Invalid command operation code" }, + { 0x0524, "Invalid field in command packet" }, + { 0x0526, "Invalid field in parameter list" }, + { 0x0600, "No reference position found" }, + { 0x0628, "Not ready to ready transition" }, + { 0x0629, "Power on, reset, or bus device reset occured" }, + { 0x0900, "Track following error" }, { 0x0901, "Tracking servo failure" }, { 0x0902, "Focus servo failure" }, @@ -360,6 +375,9 @@ { 0x1802, "Recovered data - the data was auto-reallocated" }, { 0x1803, "Recovered data with CIRC" }, { 0x1804, "Recovered data with L-EC" }, + + /* Note: These two codes (0x1805 and 0x1806) were removed + from the ATAPI 2.6 draft standard. */ { 0x1805, "Recovered data - recommend reassignment" }, { 0x1806, "Recovered data - recommend rewrite" }, @@ -374,6 +392,10 @@ { 0x2600, "Invalid field in parameter list" }, { 0x2601, "Parameter not supported" }, { 0x2602, "Parameter value invalid" }, + + + /* Note: This code (0x2603) was removed + from the ATAPI 2.6 draft standard. */ { 0x2603, "Threshold parameters not supported" }, { 0x2800, "Not ready to ready transition, medium may have changed" }, @@ -387,6 +409,8 @@ { 0x3001, "Cannot read medium - unknown format" }, { 0x3002, "Cannot read medium - incompatible format" }, + /* Note: This code (0x3700) was removed + from the ATAPI 2.6 draft standard. */ { 0x3700, "Rounded parameter" }, { 0x3900, "Saving parameters not supported" }, @@ -395,6 +419,9 @@ { 0x3f00, "ATAPI CD-ROM drive operating conditions have changed" }, { 0x3f01, "Microcode has been changed" }, + + /* Note: These two codes (0x3f02 and 0x3f03) were removed + from the ATAPI 2.6 draft standard. */ { 0x3f02, "Changed operating definition" }, { 0x3f03, "Inquiry data has changed" }, @@ -412,8 +439,9 @@ { 0x5a00, "Operator request or state change input (unspecified)" }, { 0x5a01, "Operator medium removal request" }, + /* Note: These two codes (0x5b00 and 0x5c00) were removed + from the ATAPI 2.6 draft standard. */ { 0x5b00, "Threshold condition met" }, - { 0x5c00, "Status change" }, { 0x6300, "End of user area encountered on this track" }, @@ -420,6 +448,8 @@ { 0x6400, "Illegal mode for this track" }, + { 0xb900, "Play operation oborted (sic)" }, + { 0xbf00, "Loss of streaming" }, }; #endif @@ -692,7 +722,7 @@ with this command, and we don't want to uselessly fill up the syslog. */ if (pc->c[0] != SCMD_READ_SUBCHANNEL) - printk ("%s : tray open or drive not ready\n", + printk ("%s: tray open or drive not ready\n", drive->name); } else if (sense_key == UNIT_ATTENTION) { /* Check for media change. */ @@ -1970,8 +2000,7 @@ /* If SLOT<0, unload the current slot. Otherwise, try to load SLOT. */ static int -cdrom_load_unload (ide_drive_t *drive, int slot, - struct atapi_request_sense *reqbuf) +cdrom_load_unload (ide_drive_t *drive, int slot) { /* if the drive is a Sanyo 3 CD changer then TEST_UNIT_READY (used in the cdrom_check_status function) is used to @@ -1995,9 +2024,7 @@ CDs in a multiplatter device */ struct packet_command pc; - memset (&pc, 0, sizeof (pc)); - pc.sense_data = reqbuf; pc.c[0] = LOAD_UNLOAD; pc.c[4] = 2 + (slot >= 0); @@ -2008,6 +2035,24 @@ } +/* This gets the mechanism status per ATAPI draft spec 2.6 */ +static int +cdrom_read_mech_status (ide_drive_t *drive, char *buf, int buflen) +{ + struct packet_command pc; + + memset (&pc, 0, sizeof (pc)); + + pc.buffer = buf; + pc.buflen = buflen; + pc.c[0] = MECHANISM_STATUS; + pc.c[8] = (buflen >> 8); + pc.c[9] = (buflen & 0xff); + return cdrom_queue_packet_command (drive, &pc); +} + + + int ide_cdrom_ioctl (ide_drive_t *drive, struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { @@ -2434,28 +2479,151 @@ case CDROMLOADFROMSLOT: { struct atapi_request_sense my_reqbuf; + char *buf, *buf1; int stat; - if (drive->usage > 1) - return -EBUSY; - - (void) cdrom_load_unload (drive, -1, NULL); + buf = (char *) kmalloc ( 28 * sizeof (char), GFP_KERNEL); + if (buf == NULL) return -ENOMEM; + buf1 = (char *) kmalloc ( 8 * sizeof (char), GFP_KERNEL); + if (buf1 == NULL) {kfree (buf); return -ENOMEM;} + + if (drive->usage > 1) {kfree (buf);kfree (buf1);return -EBUSY;} + stat = cdrom_mode_sense (drive, 0x2a, 0, buf, + 28 * sizeof (char), NULL); + if (stat) {kfree (buf); kfree (buf1); return stat;} + stat = cdrom_read_mech_status (drive, + buf1, 8 * sizeof (char) ); + if (stat) {kfree (buf); kfree (buf1); return stat;} + + buf[14]=( ( buf[14] & 0xe0 ) >> 5 ); + + /* This checks if the CD player is indeed a changer. */ + if ( ( buf[14] != 0x04 ) && ( buf[14] != 0x05 ) ) { + printk ("/dev/%s is not a CD Changer.\n", drive->name); + kfree (buf); kfree (buf1); return (-1); + } + kfree (buf); + stat = cdrom_check_status (drive, &my_reqbuf); + if (stat && my_reqbuf.sense_key == NOT_READY) { + printk ("/dev/%s: Requested device not " + "ready.\n", drive->name); + kfree (buf1); return (-ENOENT); + } + + /* This checks if the requested slot exceeds the maximum. */ + if ( (int) arg >= (int) buf1[5] ) { + printk ("Requested CD slot number %d, " + "does not exist in /dev/%s\n", + (int)arg, drive->name); + kfree (buf1); return (-1); + } + + /* This checks if the requested disk is the current disk, + and silently ignores the change request if this is so. */ + if ( (int)arg == (buf1[0] & 0x1f) ) + { kfree (buf1); return (stat); } + + kfree (buf1); + if ( (int) arg == -1) { + stat = cdrom_load_unload (drive, -1); + if (stat){ return stat;} cdrom_saw_media_change (drive); - if (arg == -1) { (void) cdrom_lockdoor (drive, 0, NULL); return 0; } - (void) cdrom_load_unload (drive, (int)arg, NULL); + + stat = cdrom_load_unload (drive, (int)arg); + cdrom_saw_media_change (drive); + if (stat) {return stat;} stat = cdrom_check_status (drive, &my_reqbuf); - if (stat && my_reqbuf.sense_key == NOT_READY) { - return -ENOENT; + if (stat && my_reqbuf.sense_key == NOT_READY) return -ENOENT; + + if (stat == 0 || my_reqbuf.sense_key == UNIT_ATTENTION) + return cdrom_read_toc(drive, &my_reqbuf); } - /* And try to read the TOC information now. */ - return cdrom_read_toc (drive, &my_reqbuf); + + + case CDROMMECHANISMSTATUS: { + int stat; + struct cdrom_mech_status status; + char *buf, *buf1; + + buf = (char *) kmalloc ( 28 * sizeof (char), GFP_KERNEL); + if (buf == NULL) return -ENOMEM; + buf1 = (char *) kmalloc ( 8 * sizeof (char), GFP_KERNEL); + if (buf1 == NULL) {kfree (buf); return -ENOMEM; } + + stat = verify_area (VERIFY_WRITE, (void *) arg, + sizeof (status) ); + if (stat) {kfree (buf); kfree (buf1); return stat;} + stat = cdrom_mode_sense (drive, 0x2a, 0, buf, + 28 * sizeof (char), NULL); + if (stat) {kfree (buf); kfree (buf1); return stat;} + stat = cdrom_read_mech_status(drive, buf1, 8 * sizeof (char) ); + if (stat) {kfree (buf); kfree (buf1); return stat;} + + buf[14] = ( ( buf[14] & 0xe0 ) >> 5 ); + buf[15] = ( buf[15] & 0x04 ); + + status.is_a_changer = + ( ( buf[14]==0x04 ) || ( buf[14]==0x05 ) )? 1: 0; + status.disk_present_can_lie = ( buf[15]==0x04 )? 0: 1; + status.total_slots_available = buf1[5]; + status.current_slot = ( buf1[0] & 0x1f ); + status.changer_state = ( ( buf1[0] & 0x60 ) >> 5); + status.mechanism_state = ( ( buf1[1] & 0xe0 ) >> 5); + status.fault = ( ( buf1[1] & 0x80 ) >> 7); + memcpy_tofs( (void *) arg, &status, sizeof (status) ); + + kfree (buf); kfree (buf1); + return stat; } + + case CDROMREADSLOTTABLE: { + int stat, total_slots; + struct cdrom_slot_table table; + char *buf; + + memcpy_fromfs (&table, (void *) arg , sizeof (table)); + + buf = (char *) kmalloc ( 8 * sizeof (char), GFP_KERNEL); + if (buf == NULL) return -ENOMEM; + + stat = cdrom_read_mech_status(drive, buf, 8 * sizeof (char) ); + if (stat) {kfree (buf); return stat;} + total_slots = buf[5]; + kfree (buf); + + /* This checks if the requested slot exceeds the maximum. */ + if ( ( table.slot >= total_slots ) || ( table.slot < 0 ) ) { + printk ("Requested CD slot number %d, " + "does not exist in /dev/%s\n", table.slot, drive->name); + return (-1); + } + stat = verify_area (VERIFY_WRITE, (void *) arg, + sizeof (table) ); + if (stat) return stat; + + buf = (char *) kmalloc ( ( 8 + ( 4 * total_slots ) ) * + sizeof (char), GFP_KERNEL); + if (buf == NULL) return -ENOMEM; + + stat = cdrom_read_mech_status (drive, buf, + ( 8 + ( 4 * total_slots ) ) * sizeof (char) ); + if (stat) {kfree (buf); return stat;} + + table.disk_was_changed = ( buf[(8+(4*table.slot))] & 0x01); + table.disk_present = ( ( buf[(8+(4*table.slot))] & 0x80 ) >> 7); + + memcpy_tofs( (void *) arg, &table, sizeof (table) ); + kfree (buf); + return stat; + } + + #if 0 /* Doesn't work reliably yet. */ case CDROMRESET: { diff -u --recursive --new-file /home/andersee/cdrom.h linux/include/linux/cdrom.h --- /home/andersee/cdrom.h Mon Aug 12 11:19:35 1996 +++ linux/include/linux/cdrom.h Tue Aug 13 06:22:24 1996 @@ -281,7 +281,42 @@ /* * For controlling a changer. (Used by ATAPI driver.) */ -#define CDROMLOADFROMSLOT 0x531a /* LOAD disk from slot*/ +#define CDROMLOADFROMSLOT 0x531a /* LOAD disk from slot */ +#define CDROMMECHANISMSTATUS 0x531b /* (struct cdrom_mech_status) */ +#define CDROMREADSLOTTABLE 0x531c /* (struct cdrom_slot_table) */ + +struct cdrom_mech_status +{ + int is_a_changer; /* 1=a changer, 0=not a changer */ + int total_slots_available; /* Total number of slots available, + for a Non-changer, this is 1 */ + int current_slot; /* 0 based index of the currently + selected changer slot */ + int changer_state; /* 0x0h=Ready, 0x1h=Load in Progress, + 0x2h=Unload in Progress, 0x3h=Initializing */ + int mechanism_state; /* 0x0h=Idle, 0x1h=Active with Audio port + in use (Playing, Paused), + 0x2h=Audio Scan in progress, + 0x3h=Active with host, composite, or + other ports in use (READ, PLAY CD, etc...), + 0x4h-0x6h Reserved, 0x7h No Information Available */ + int fault; /* Changer failed to complete operation + reported in changer state */ + int disk_present_can_lie; /* Device dependent - The changer may report + disk_in_slot=1 for ALL slots until it can + determine that there is no disk present + in a slot by using CDROMLOADFROMSLOT */ +}; + +struct cdrom_slot_table +{ + int slot; /* This must be set to the desired slot, + before the ioctl call */ + int disk_present; /* Array of size total_slots_available, + 1=Disk is in slot, 0=no disk in slot */ + int disk_was_changed; /* Array of size total_slots_available, + 1=Disk was changed, 0=disk was not changed */ +}; /* diff -u --recursive --new-file /home/andersee/ide-cd linux/Documentation/cdrom/ide-cd --- /home/andersee/ide-cd Wed Aug 14 00:07:21 1996 +++ linux/Documentation/cdrom/ide-cd Wed Aug 14 00:05:54 1996 @@ -348,16 +348,18 @@ bug. -6. cdload.c +6. cdchange.c ----------- /* - * cdload.c + * cdchange.c * - * Load a cdrom from a specified slot in a changer. The drive should be - * unmounted before executing this. + * This load a cdrom from a specified slot in a changer, and displays + * information about the changer status. The drive should be unmounted before + * utilizing this program. * * Based on code originally from Gerhard Zuber . + * Major changes introduced by Erik Andersen . */ #include @@ -371,21 +373,24 @@ int main (int argc, char **argv) { + struct cdrom_mech_status mechanism; + struct cdrom_slot_table slot_table; char *program; char *device; int x_slot; - int fd; /* file descriptor for CD-ROM device */ + int fd, i; /* file descriptor for CD-ROM device */ int status; /* return status for system calls */ program = argv[0]; if (argc != 3) { - fprintf (stderr, "usage: %s \n", program); + fprintf (stderr, "usage: %s \n" + "slots are numbered 1-n\n", program); exit (1); } device = argv[1]; - x_slot = atoi (argv[2]); + x_slot = atoi (argv[2])-1; /* open device */ fd = open (device, 0); @@ -395,6 +400,21 @@ exit (1); } + /* Check CD player status */ + status = ioctl (fd, CDROMMECHANISMSTATUS, &mechanism); + if (status != 0) { + fprintf (stderr, + "%s: CDROMMECHANISMSTATUS ioctl failed for `%s': %s\n", + program, device, strerror (errno)); + exit (1); + } + + if (mechanism.is_a_changer==0) { + fprintf (stderr, "%s: Device `%s' is not an ATAPI " + "compliant CD changer.\n", program, device); + exit (1); + } + /* load */ status = ioctl (fd, CDROMLOADFROMSLOT, x_slot); if (status != 0) { @@ -404,6 +424,60 @@ exit (1); } + status = ioctl (fd, CDROMMECHANISMSTATUS, &mechanism); + if (status != 0) { + fprintf (stderr, + "%s: CDROMMECHANISMSTATUS ioctl failed for `%s': %s\n", + program, device, strerror (errno)); + exit (1); + } + fprintf (stderr, "current_slot: %d\n", mechanism.current_slot+1); + fprintf (stderr, "total_slots_available: %d\n", + mechanism.total_slots_available); + if (mechanism.changer_state==0) + fprintf (stderr, "changer_state: Ready.\n"); + else if (mechanism.changer_state==1) { + fprintf (stderr, "changer_state: Load in Progress.\n"); + } + else if (mechanism.changer_state==2) { + fprintf (stderr, "changer_state: Unload in Progress.\n"); + } + else if (mechanism.changer_state==3) { + fprintf (stderr, "changer_state: Initializing.\n"); + } + if (mechanism.mechanism_state==0) + fprintf (stderr, "mechanism_state: Idle.\n"); + else if ((mechanism.mechanism_state>=1) && + (mechanism.mechanism_state<=3)) { + fprintf (stderr, "mechanism_state: Device in Use.\n"); + } + else if (mechanism.mechanism_state>=4) { + fprintf (stderr,"mechanism_state: No Information Available.\n"); + } + if (mechanism.disk_present_can_lie==1) + fprintf (stderr, "disk_present_can_lie: True.\n"); + else fprintf (stderr, "disk_present_can_lie: False.\n"); + + + for (i=0; i