/* * cdrom.c * * Description: * Low-level cdrom support for mountiso9660 * * History: * rogerc 12/18/90 Created * rogerc 02/12/92 Added support for 512 byte CDs * rogerc 04/01/92 Added support for mode 2 CDs */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cdrom.h" #include "util.h" #define G6_DISCINFO 0xc7 #define CD_SENSELEN 18 #define STA_RETRY (-1) #define BLOCK_LIMIT 0x1000 /* max blocks transferable in 1 read op */ #ifdef DEBUG #define CDDBG(x) {if (cddebug) {x;}} #define CACHEDBG(x) {if (cachedebug) {x;}} #else #define CDDBG(x) #define CACHEDBG(x) #endif /* * If we are to support discs with block sizes larger than 2048, a LOT * of stuff is going to get more complicated. Beware that some automatic * buffers are of size 2048 and would be overflowed, and since as of * this writing the CD-ROM drives don't support block sizes other than * these three, reading a block will get more complicated. * * If block sizes smaller than 512 are ever supported, the caching * scheme will have to be reworked as well. */ #define ISVALIDBLKSIZE(sz) ((sz) == 2048 || (sz) == 1024 || (sz) == 512) #define MSELPARMLEN 12 #define EFS_BLKSIZE 512 #define min(a,b) ((a) < (b) ? (a) : (b)) enum Brand { GENERIC, TOSHIBA, SONY, TOSHIBA_SCSI2, TOSHIBA_PHOTO }; char *BrandName[] = { "GENERIC", "TOSHIBA", "SONY", "TOSHIBA_SCSI2", "TOSHIBA_PHOTO" }; struct cdrom { int dkfd; char scsiname[PATH_MAX]; char *cache; /* Store for the cache */ int *block_num; /* Array of cache block #'s */ int cache_blocks; enum Brand type; int blksize; char density; unsigned long voldesc; /* loc. of volume descriptor */ u_char pglengths[ALL+1]; /* lengths of each page for */ /* mode select, sense. */ /* len of 0 means page not */ /* supported */ }; typedef struct tagMselParams { /* * four bytes of header info... */ unsigned char res1; unsigned char mediumType; unsigned char devSpecific; unsigned char blockDescLen; /* * Block descriptor */ unsigned int densityCode : 8; unsigned int numBlocks : 24; unsigned int res2 : 8; unsigned int blockSize : 24; } MSELPARAM; typedef struct mode_sense_data sndata_t; /* * Determines whether or not the device really is a CD-ROM device * vs. some other device which contains an ISO9660 filesystem, like * a disk for example. */ int really_is_CDROM = 1; /* * This goes in the blockDescLen field above */ #define BLOCKDESCLEN 8 int cddebug = 0; int cachedebug = 0; /* * Caching statistics */ int cache_hits; int cache_misses; static int read_block(CDROM *cd, long block, char *buf, int num_blocks, int use_cache, char *reason, int lineno); static int set_blksize( CDROM *cd, int blksize ); static int set_up_volume_header(int dkfd); static int read_from_cache( CDROM *cd, int start_block, int num_blocks, char *buf ); static char * find_block( CDROM *cd, int block ); static void write_to_cache( CDROM *cd, int start_block, int num_blocks, char *buf ); static void insert_block( CDROM *cd, int block, char *buf ); static void act_like_a_cdrom( CDROM *cd, struct dsreq *dsp ); static scsi_modesense(CDROM *cd, sndata_t *addr, u_char pgcode); static void scsi_getsupported(CDROM *cd); static void prevent_removal( struct dsreq *dsp ); static void allow_removal( struct dsreq *dsp ); extern int fd_to_scsi_name(int, char*); /* * used to convert all the 3 byte sequences from scsi * modesense, etc. to uints for printing, etc. */ static uint sc_3bytes_uint(u_char *a) { return (((uint)a[0]) << 16) + (((uint)a[1]) << 8) + (uint)a[2]; } /* * void * cd_init( ) * * Description: * * Parameters: */ void cd_init( ) { return; } static dsreq_t * cd_dsopen(CDROM *cd) { int i; CDDBG(fprintf(stderr, ">>>>cd: cd_dsopen:\n")); /* * retry for ~20 seconds */ for (i = 0; i < 200; i++) { dsreq_t *dsp; dsp = dsopen(cd->scsiname, O_RDONLY); if (dsp) return dsp; if (errno != EBUSY) { CDDBG(fprintf(stderr, ">>>>cd: cd_dsopen: dsopen(\"%s\") failed, errno/%d\n", cd->scsiname, errno)); break; } sginap(i); } return NULL; } /* * Convert to and from Binary Coded Decimal */ #define TWODIGITBCDTOINT( bcd ) (((bcd) >> 4) * 10 + ((bcd) & 0xf)) #define INTTOTWODIGITBCD( i ) ((((i) / 10) << 4) | ((i) % 10)) typedef struct tag_discinfo { u_char discid; u_char minute; u_char sec; u_char frame; } DISCINFO; static void find_voldesc(CDROM *cd, dsreq_t *dsp) { int minute, sec, frame; int diskInfoBuf[(sizeof(DISCINFO) + 3) / sizeof(int)]; DISCINFO *info = (DISCINFO *)diskInfoBuf; cd->voldesc = 16 * CDROM_BLKSIZE; CDDBG(fprintf(stderr, ">>>>cd: find_voldesc: default = %d\n", cd->voldesc)); if (cd->type == TOSHIBA_PHOTO) { fillg1cmd(dsp, (uchar_t *)CMDBUF(dsp), G6_DISCINFO, 0x3, 0, 0, 0, 0, 0, 0, 0, 0); filldsreq(dsp, (unsigned char *)info, sizeof(DISCINFO), DSRQ_READ|DSRQ_SENSE ); if ( doscsireq(getfd(dsp), dsp) < 0 || STATUS(dsp) != STA_GOOD) { return; } minute = TWODIGITBCDTOINT(info->minute); sec = TWODIGITBCDTOINT(info->sec); frame = TWODIGITBCDTOINT(info->frame); if (minute || sec || frame) { cd->voldesc = ( ((minute * 60) + sec) * 75 + frame - 150 + 16) * CDROM_BLKSIZE; } } CDDBG(fprintf(stderr, ">>>>cd: find_voldesc: cd->voldesc = %d\n", cd->voldesc)); } unsigned long cd_voldesc(CDROM *cd) { return cd->voldesc; } /* * int * cd_open( char *dev, int num_blocks, CDROM **cdp ) * * Description: * Open dev with dsopen, to use devscsi interface. * * Parameters: * dev device to open * num_blocks number of blocks to cache for this device * cdp Gets pointer to CDROM structure if successful * * Returns: * 0 if successful, error code otherwise */ int cd_open( char *dev, int num_blocks, CDROM **cdp ) { static char *toshiba = "TOSHIBA CD"; static char *sony = "SONY CD"; int model; int dkfd; char scsiname[PATH_MAX]; unsigned char inquiry[98]; enum Brand type = GENERIC; CDROM *cd; struct dk_ioctl_data arg; struct dsreq *dsp; int initially_is_CDROM = 1; /* assume it is initially */ CDDBG(fprintf(stderr, ">>>>cd: cd_open: dsdebug set to 0%o\n", dsdebug)); dkfd = open(dev, O_RDONLY); if (dkfd < 0) { fprintf(stderr, "mount_iso9660: ERROR: Unable to open(\"%s\"), errno = %d\n", dev, errno); return errno; } if (fd_to_scsi_name(dkfd, scsiname) < 0) { fprintf(stderr, "mount_iso9660: ERROR: Unable to translate to scsiname for dev(\"%s\"), errno = %d\n", dev, errno); return errno ? errno : ENOENT; } dsp = dsopen(scsiname, O_RDONLY); if (dsp) { if (inquiry12(dsp, (caddr_t)inquiry, sizeof (inquiry), 0 ) == 0) { /* snap initial peripheral device type and removable media bit */ initially_is_CDROM = (inquiry[0] & 0x1f) == 5/* CD-ROM device */ || (inquiry[1] & 8/* removable media */); if (strncmp((const char *)inquiry + 8, toshiba, strlen(toshiba)) == 0) { /* * This is really ugly. The purpose of doing * this model number hack as opposed to simply * doing a strcmp with "TOSHIBA CD-ROM * XM-3401TA" is to support multi-session CDs * on future versions of the drive. */ /* * Null terminate model for atoi. */ inquiry[30] = 0; /* * This blows away full model info */ model = atoi(inquiry + 26); if (model >= 3401) { type = TOSHIBA_PHOTO; } else { type = ((inquiry[2] & 0x7) == 2) ? TOSHIBA_SCSI2 : TOSHIBA; } } else if (strncmp((const char *)inquiry + 8, sony, strlen(sony)) == 0) { type = SONY; } } else { type = GENERIC; } } else { fprintf(stderr, "mount_iso9660: ERROR: Unable to dsopen(\"%s\"), errno = %d\n", scsiname, errno); return errno; } if (type == GENERIC) { really_is_CDROM = initially_is_CDROM; /* * too many customers with 3rd party drives took this as * an error message, so make it clearer that it's just a * warning */ if (really_is_CDROM) fprintf(stderr, "mount_iso9660: Note: unknown CD-ROM type may" " not function correctly,\n\tInquiry12 type = <%s>\n", inquiry + 8); else fprintf(stderr, "mount_iso9660: Note: ISO9660 filesystem on SCSI" " disk may not function correctly,\n\tInquiry12" " type = <%s>\n", inquiry + 8); } if (set_up_volume_header(dkfd) == 0) { fprintf(stderr, "mount_iso9660: ERROR: set_up_volume_header failed for" " dkfd = %d, errno = %d\n", dkfd, errno); dsclose(dsp); CDDBG(fprintf(stderr, ">>>>cd: cd_open: dsclose called @ line #%d\n", __LINE__)); return errno; } fcntl(getfd(dsp), F_SETFD, FD_CLOEXEC ); cd = safe_malloc(sizeof (*cd)); bzero(cd, sizeof *cd); cd->cache_blocks = num_blocks; cd->dkfd = dkfd; cd->type = type; strcpy(cd->scsiname, scsiname); CDDBG(fprintf(stderr, ">>>>cd: cd_open: cd->type is %s\n", BrandName[cd->type])); act_like_a_cdrom(cd, dsp); /* * Do another inquiry to see if we can once and forall determine * if this is really a CD-ROM device. */ if (!initially_is_CDROM && inquiry12(dsp, (caddr_t)inquiry, sizeof (inquiry), 0 ) == 0) { really_is_CDROM = (inquiry[0] & 0x1f) == 5/* CD-ROM device */ || (inquiry[1] & 8/* removable media */); } scsi_getsupported(cd); /* * This might get reset later. It needs to start as CDROM_BLKSIZE, * so we can read the volume descriptor from the disc. After * reading the volume descriptor, iso.c will figure out the block * size to use from then on out and set it using cd_set_blksize. */ cd->blksize = CDROM_BLKSIZE; if (num_blocks) { cd->cache = safe_malloc(CDROM_BLKSIZE * num_blocks); /* * Allocate 4 times as much in case we have 512 byte * blocks; then we'll need 4 times as many block numbers * if we don't want to waste 75% of the cache. */ cd->block_num = safe_malloc(sizeof(int) * num_blocks * 4); bzero(cd->block_num, sizeof(int) * num_blocks * 4); } prevent_removal(dsp); /* * XXX The logic from here until the comment "Put drive in high * speed mode" should be cleaned up; The call to set_blksize * should come after the CD-ROM XA specific code, which would * render the modeselect15 unnecessary. * * I'm not fixing this now because it's very close to the time * we're shipping 5.2; I just pulled this call to set_blksize out * of the if statement to fix a problem with the Sony drive and I * noticed this. -- rogerc 1/28/94 */ cd->density = 0; if ( ! set_blksize(cd, cd->blksize)) { fprintf(stderr, "mount_iso9660: ERROR: set_blksize failed for blksize = %d, errno = %d\n", cd->blksize, errno); dsclose(dsp); CDDBG(fprintf(stderr, ">>>>cd: cd_open: dsclose called @ line #%d\n", __LINE__)); free(cd); return EIO; } /* * The Toshiba SCSI2 drives support CD-ROM XA discs, which can * contain ISO 9660 file systems. These discs use different * ECCs and headers, and we may need to use modeselect appropriately. */ if ( cd->type == TOSHIBA_SCSI2 || cd->type == TOSHIBA_PHOTO) { #define HEADER_SIZE 8 int headerBuf[(HEADER_SIZE + 3) / sizeof(int)]; uchar_t *header = (uchar_t *)headerBuf; MSELPARAM params; CDDBG(fprintf(stderr, ">>>>cd: TOSHIBA_SCSI2/TOSHIBA_PHOTO 0x44 cmd:\n")); fillg1cmd( dsp, (uchar_t *)CMDBUF(dsp), 0x44, 2, 0, 0, 0, 16, 0, 0, 8, 0 ); filldsreq( dsp, header, HEADER_SIZE, DSRQ_READ|DSRQ_SENSE ); doscsireq( getfd( dsp ), dsp ); if (header[0] == 2) { cd->density = 0x81; bzero(¶ms, sizeof params); params.blockDescLen = BLOCKDESCLEN; params.blockSize = cd->blksize; params.densityCode = cd->density; arg.i_addr = (caddr_t)¶ms; arg.i_len = sizeof params; arg.i_page = 0; if (ioctl(cd->dkfd, DIOCSELFLAGS, 0x10) != 0) { fprintf(stderr, "mount_iso9660: ERROR: ioctl(DIOCSELFLAGS, 0x10) failed, errno = %d\n", errno); return EIO; } if (ioctl(cd->dkfd, DIOCSELECT, &arg)) { fprintf(stderr, "mount_iso9660: ERROR: ioctl(DIOCSELECT) failed, errno = %d\n", errno); return EIO; } } } /* * Put drive in high speed mode */ if (cd->type == TOSHIBA_PHOTO) { struct { unsigned char reserved1; unsigned char mediumType; unsigned char deviceParam; unsigned char blockDescLen; unsigned char pageCode; unsigned char pageLength; unsigned char speed; } params; bzero(¶ms, sizeof params); params.pageCode = 0x20; params.pageLength = 1; params.speed = 1; arg.i_addr = (caddr_t)¶ms; arg.i_len = sizeof params; arg.i_page = 0x20; if (ioctl(cd->dkfd, DIOCSELFLAGS, 0x10) != 0) { fprintf(stderr, "mount_iso9660: ERROR: ioctl(DIOCSELFLAGS, 0x10) failed, errno = %d\n", errno); return EIO; } if (ioctl(cd->dkfd, DIOCSELECT, &arg) != 0) { fprintf(stderr, "mount_iso9660: ERROR: ioctl(DIOCSELECT) failed, errno = %d\n", errno); perror("cd_open DIOCSELECT"); (void)close(cd->dkfd); return EIO; } } find_voldesc(cd, dsp); *cdp = cd; dsclose(dsp); CDDBG(fprintf(stderr, ">>>>cd: cd_open: dsclose called @ line #%d\n", __LINE__)); CDDBG( { int i; fprintf(stderr, ">>>>cd:.. cd->dkfd/%d\n", cd->dkfd); fprintf(stderr, ">>>>cd:.. cd->scsiname<%s>\n", cd->scsiname); fprintf(stderr, ">>>>cd:.. cd->cache/0x%x\n", cd->cache); fprintf(stderr, ">>>>cd:.. cd->block_num/0x%x\n", cd->block_num); fprintf(stderr, ">>>>cd:.. cd->cache_blocks/%d\n", cd->cache_blocks); fprintf(stderr, ">>>>cd:.. cd->type/%d\n", cd->type); fprintf(stderr, ">>>>cd:.. cd->blksize/%d\n", cd->blksize); fprintf(stderr, ">>>>cd:.. cd->density/%d\n", cd->density); fprintf(stderr, ">>>>cd:.. cd->voldesc/%u\n", cd->voldesc); for (i = 0; i < (ALL+1); i++) fprintf(stderr, ">>>>cd:.. pg[0x%x]: length/%d\n", i, cd->pglengths[i]); } ); return 0; } int cd_close( CDROM *cd ) { struct dsreq *dsp; CDDBG(fprintf(stderr, ">>>>cd: cd_close:\n")); errno = 0; CDDBG(fprintf(stderr, ">>>>cd: cd_close: calling cd_dsopen from line #%d\n", __LINE__)); if ((dsp = cd_dsopen(cd)) == NULL) { fprintf(stderr, "mount_iso9660: Unable to re-dsopen(\"%s\"), errno = %d\n", cd->scsiname, errno); return errno; } allow_removal(dsp); dsclose(dsp); CDDBG(fprintf(stderr, ">>>>cd: cd_close: dsclose called @ line #%d\n", __LINE__)); /* * This is a hack; it makes set_blksize call modeselect with the * right parameters to read mode 1 discs instead of mode 2 discs * in case we had previously set it for mode 1 discs. */ cd->density = 0; set_blksize(cd, EFS_BLKSIZE ); return errno; } /* * int * cd_stat( CDROM *cd, struct stat *sb ) * * Description: * Do a stat() on the file descriptor for cd->dkfd * * Parameters: * cd CDROM device * sb buffer for stat info * * Returns: * 0 if successful, error code otherwise */ int cd_stat( CDROM *cd, struct stat *sb ) { CDDBG(fprintf(stderr, ">>>>cd: cd_stat:\n")); if (fstat( cd->dkfd, sb ) < 0) return errno; return 0; } /* * int * cd_is_dsp_fd( CDROM *cd, int fd ) * * Description: * Find out if we care about fd * * Parameters: * cd * fd * * Returns: * 1 if we care, 0 if we don't */ int cd_is_dsp_fd( CDROM *cd, int fd ) { return fd == cd->dkfd; } /* * cd_read * * Description: * Read count bytes starting at offset into vdata from CD-ROM * * Parameters: * cd CD to read from * offset offset on CD from which to read * vdata buffer to receive bytes * count Number of bytes to read * * Returns: * 0 if successful, error code otherwise */ int cd_read( CDROM *cd, unsigned long offset, void *vdata, unsigned long count, int use_cache, char *reason, int lineno) { unsigned int slop; int error, num_read; char buf[CDROM_BLKSIZE], *data = vdata; CDDBG(fprintf(stderr, ">>>>cd: cd_read: offset/%u, vdata/0x%x, count/%u, use_cache/%d line/%d<%s>\n", offset, vdata, count, use_cache, lineno, reason)); /* * Read that portion of the beginning that is not aligned on a * block boundary */ slop = offset % cd->blksize; if (slop) { error = read_block( cd, offset / cd->blksize, buf, 1, use_cache, "cd_read", __LINE__); if (error) { CDDBG(fprintf(stderr, ">>>>cd: cd_read: read_block returned error/%d @ line # %d\n", error, __LINE__)); return (error); } num_read = min( cd->blksize - slop, count ); count -= num_read; memcpy( data, buf + slop, num_read ); data += num_read; offset += cd->blksize - 1; offset -= offset % cd->blksize; } /* * Read the block aligned middle portion */ if (count / cd->blksize) { error = read_block( cd, offset / cd->blksize, data, count / cd->blksize, use_cache, "cd_read", __LINE__); if (error) { CDDBG(fprintf(stderr, ">>>>cd: cd_read: read_block returned error/%d @ line # %d\n", error, __LINE__)); return (error); } offset += count - count % cd->blksize; } /* * Read that portion of the end that is not aligned on a block * boundary. This avoids writing past the buffer */ slop = count % cd->blksize; if (slop) { data += count - slop; offset += count / cd->blksize; error = read_block( cd, offset / cd->blksize, buf, 1, use_cache, "cd_read", __LINE__); if (error) { CDDBG(fprintf(stderr, ">>>>cd: cd_read: read_block returned error/%d @ line # %d\n", error, __LINE__)); return (error); } memcpy( data, buf, slop ); } return (0); } /* * int * cd_read_file( CDROM *cd, CD_FILE *fp, unsigned int offset, * unsigned int count, void *vbuf ) * * Description: * Read from a file, using a CD_FILE structure. This function knows * how to read in interleaved mode, and takes extended attributes in * to consideration * * Parameters: * cd Device to read from * fp File to read from * offset into file * count bytes to read * vbuf buffer to read into * * Returns: * 0 if successful, error code otherwise */ int cd_read_file( CDROM *cd, CD_FILE *fp, unsigned int offset, unsigned int count, void *vbuf ) { int block, error, num_read, block_offset, num_blocks; unsigned int slop; char slop_buf[CDROM_BLKSIZE], *buf; CDDBG(fprintf(stderr, ">>>>cd: cd_read_file: fpblock/%d, fpxattrlen/%d, fpintgap/%d, fpfusize/%d offset/%u, count/%u, vbuf/0x%x\n", fp->block, fp->xattr_len, fp->int_gap, fp->fu_size, offset, count, vbuf)); buf = vbuf; slop = offset % cd->blksize; if (slop) { block_offset = offset / cd->blksize; block = fp->block + fp->xattr_len + (fp->xattr_len ? fp->int_gap : 0) + block_offset + (fp->fu_size ? ((block_offset / fp->fu_size) * fp->int_gap) : 0); error = read_block( cd, block, slop_buf, 1, 0, "cd_read_file", __LINE__); if (error) return (error); num_read = min( cd->blksize - slop, count ); count -= num_read; memcpy( buf, slop_buf + slop, num_read ); buf += num_read; offset += cd->blksize - 1; offset -= offset % cd->blksize; } while (count >= cd->blksize) { block_offset = offset / cd->blksize; block = fp->block + fp->xattr_len + (fp->xattr_len ? fp->int_gap : 0) + block_offset + (fp->fu_size ? ((block_offset / fp->fu_size) * fp->int_gap) : 0); slop = fp->fu_size ? (offset % (cd->blksize * fp->fu_size)) : 0; num_blocks = fp->fu_size ? fp->fu_size - slop : count / cd->blksize; if (num_blocks > count / cd->blksize) num_blocks = count / cd->blksize; error = read_block( cd, block, buf, num_blocks, 0, "cd_read_file", __LINE__); if (error) return (error); num_read = num_blocks * cd->blksize; buf += num_read; offset += num_read; count -= num_read; } /* * Read that portion of the end that is not aligned on a block * boundary. This avoids writing past the buffer */ if (count) { block_offset = offset / cd->blksize; block = fp->block + fp->xattr_len + (fp->xattr_len ? fp->int_gap : 0) + block_offset + (fp->fu_size ? ((block_offset / fp->fu_size) * fp->int_gap) : 0); error = read_block( cd, block, slop_buf, 1, 0, "cd_read_file", __LINE__); if (error) return (error); memcpy( buf, slop_buf, count ); } return (0); } /* * int * cd_media_changed( CDROM *cd, int *changed ) * * Description: * Find out if the media could have changed recently * Also makes sure there's a disc in the drive * * Parameters: * cd * changed receives 0 if not changed, 1 if changed * * Returns: * 0 if successful, error code otherwise * * Because the open is moderately expensive on most systems * and even testunitready costs 2-4 msecs, we only do this every * few seconds. We can't just keep the devscsi device open, * because that can cause problems for other programs, like SoftWindows. * Should probably be removed altogether, but it's a bit late in the * release cycle for that. * See bug #544222 * Olson, 11/97 */ int cd_media_changed( CDROM *cd, int *changed ) { int retries; static time_t lasttime; time_t thistime; struct dsreq *dsp; CDDBG(fprintf(stderr, ">>>>cd: cd_media_changed:\n")); *changed = 0; thistime = time(NULL); if (thistime < (lasttime+3)) return 0; CDDBG(fprintf(stderr, ">>>>cd: cd_media_changed: calling cd_dsopen from line #%d\n", __LINE__)); if ((dsp = cd_dsopen(cd)) == NULL) { fprintf(stderr, "mount_iso9660: Unable to re-dsopen(\"%s\"), errno = %d\n", cd->scsiname, errno); return errno; } retries = 2; while (retries--) { testunitready00( dsp ); if ( STATUS(dsp) == STA_CHECK && SENSEBUF(dsp)[2] == 6) { *changed = 1; if (SENSEBUF(dsp)[12] == 0x29) act_like_a_cdrom(cd, dsp); } else if (STATUS(dsp) == STA_GOOD) { break; } else { cd_flush_cache(cd); dsclose(dsp); CDDBG(fprintf(stderr, ">>>>cd: cd_media_changed: dsclose called @ line #%d\n", __LINE__)); return EIO; } } lasttime = thistime; /* only reset on good status returns */ dsclose(dsp); CDDBG(fprintf(stderr, ">>>>cd: cd_media_changed: dsclose called @ line #%d\n", __LINE__)); return 0; } /* * int * cd_set_blksize(CDROM *cd, int blksize) * * Description: * Set the block size to be used with this particular * CD-ROM drive * * Parameters: * cd CD-ROM drive of which to set block size * blksize The block size * * Returns: * 0 if successful, errno otherwise */ int cd_set_blksize(CDROM *cd, int blksize) { CDDBG(fprintf(stderr, ">>>>cd: cd_set_blksize: blksize/%d\n", blksize)); if (!ISVALIDBLKSIZE(blksize)) { CDDBG(fprintf(stderr, ">>>>cd: cd_set_blksize: ! valid blksize %d\n", blksize)); return EIO; } /* * Scale the number of blocks to be stored in the cache * according to the size of the blocks. This will not work * as currently coded if you try to support a block size * that's smaller than 512 bytes. */ cd->cache_blocks = cd->cache_blocks * cd->blksize / blksize; cd->blksize = blksize; set_blksize(cd, blksize); return 0; } /* * int * cd_get_blksize(CDROM *cd) * * Description: * Return cd->blksize; keep CDROM struct opaque * * Parameters: * cd * * Returns: * block size we're using for this CD-ROM drive */ int cd_get_blksize(CDROM *cd) { return cd->blksize; } /* * void * flush_cache(CDROM *cd) * * Description: * Empty out the cache - next read guaranteed to miss the cache * * Parameters: * cd */ void cd_flush_cache(CDROM *cd) { CDDBG(fprintf(stderr, ">>>>cd: cd_flush_cache:\n")); bzero(cd->block_num, sizeof(int) * cd->cache_blocks); } /* * static int * read_block( CDROM *cd, long block, char *buf, int num_blocks, * int use_cache ) * * Description: * Read num_block blocks from CD-ROM into buf, starting at block * * Parameters: * cd * block starting block from which to read * buf buffer to receive blocks * num_blocks number of blocks to read * use_cache Whether or not to write blocks to cache * * Returns: * 0 if successful, error code otherwise */ /* ARGSUSED */ static int read_block( CDROM *cd, long block, char *buf, int num_blocks, int use_cache, char *reason, int lineno) { int blocks, orgblock, orgnum_blocks; int rc; char *orgbuf; orgblock = block; orgnum_blocks = num_blocks; orgbuf = buf; CDDBG(fprintf(stderr, ">>>>cd: read_block: block/%d, buf/0x%x, #_blocks/%d, use_cache/%d line/%d<%s>\n", block, buf, num_blocks, use_cache, lineno, reason)); /* * We'll look in the cache even if use_cache == 0; there's no point * in not checking. We will not write to cache in this case. */ if (read_from_cache( cd, block, num_blocks, buf )) { CDDBG(fprintf(stderr, ">>>>cd: read_block: read_from_cache success!\n")); cache_hits += num_blocks; return (0); } cache_misses += num_blocks; if (lseek(cd->dkfd, block * cd->blksize, L_SET) == -1) { CDDBG(fprintf(stderr, ">>>>cd: read_block: lseek returned errno/%d\n", errno)); return errno; } /* * Do the read, in increments of BLOCK_LIMIT, which is the largest * number of blocks we can read with one call. Check each time for * success */ for ( ; num_blocks > 0; num_blocks -= BLOCK_LIMIT, buf += BLOCK_LIMIT, block += BLOCK_LIMIT) { blocks = min( num_blocks, BLOCK_LIMIT ); rc = read(cd->dkfd, buf, blocks * cd->blksize); if (rc < 0) { CDDBG(fprintf(stderr, ">>>>cd: read_block: read returned errno/%d\n", errno)); return errno; } else { CDDBG(fprintf(stderr, ">>>>cd: read_block: read returned bytes/%d\n", rc)); } } if (use_cache) write_to_cache( cd, orgblock, orgnum_blocks, orgbuf ); return (0); } /* * static int * set_blksize( CDROM *cd, struct dsreq *dsp, blksize ) * * Description: * Set the block size of dsp to blksize * * Parameters: * cd devscsi struct of device to set block size of * blksize the new size * * Returns: * 1 if successful, 0 otherwise */ static int set_blksize( CDROM *cd, int blksize ) { MSELPARAM params; sndata_t data; struct dk_ioctl_data arg; if (!really_is_CDROM) { return 1; } CDDBG(fprintf(stderr, ">>>>cd: set_blksize: cd/0x%x, blksize/%d\n", cd, blksize)); /* * some devices, like maxtor optical won't return default if no media * in drive (or unformatted), but will return current... This is * supposed to be fixed in new firmware soon, but it doesn't cost * anything but a kernel error message to try both. */ if ( scsi_modesense(cd, &data, ALL|CURRENT) && scsi_modesense(cd, &data, ALL|DEFAULT) ) { fprintf(stderr, "Unable to modesense the drive for blocksize.\n"); return 0; } if (data.bd_len > 7) { /* * Note that blockSize is the *logical* block size, and if * available, is preferred over the *physical* sector size * in f_bytes_sec, which may be different. (In fact, this * is the case with syquest drives, and also with the * Maxtor Tahiti II.) */ params.densityCode = data.block_descrip[0]; params.blockSize = sc_3bytes_uint(&data.block_descrip[5]); } else { fprintf(stderr, "Unable to determine blocksize/density for the drive.\n"); return 0; } /* * If things are already set to what they're supposed to be, * we don't need to do anything. */ if ( params.blockSize == blksize && params.densityCode == cd->density) { return 1; } CDDBG(fprintf(stderr, ">>>>cd: set_blksize: params.blockSize/%d != blksize/%d || params.densityCode/%d != cd->density/%d\n", params.blockSize, blksize, params.densityCode, cd->density)); /* * set the new blksize if we can. */ bzero(¶ms, sizeof params); params.blockDescLen = BLOCKDESCLEN; params.densityCode = cd->density; params.blockSize = blksize; arg.i_addr = (caddr_t) ¶ms; arg.i_len = sizeof params; arg.i_page = 0; if (ioctl(cd->dkfd, DIOCSELFLAGS, 0x10) != 0) { CDDBG(fprintf(stderr, ">>>>cd: set_blksize: ioctl(DIOCSELFLAGS, 0x10) failed, errno/%d\n", errno)); return 0; } if (ioctl(cd->dkfd, DIOCSELECT, &arg) != 0) { CDDBG(fprintf(stderr, ">>>>cd: set_blksize: ioctl(DIOCSELECT, &arg) failed, errno/%d\n", errno)); return 0; } return 1; /* success */ } static int set_up_volume_header(int dkfd) { int capacity = 0; struct volume_header vh; /* * Always create a volume header. The previous disc may have been * smaller or larger than this one. * * The setting of heads and sectors is a gross hack for sw95 * and softwindows up to v4.0, because they tried to figure out * if a CDROM was really a CDROM by looking for the old dp_secs * and dp_trks0 fields (AKA _dp_heads and _dp_sect in 6.4). * The algorithm was rather flawed, but without this, those versions * won't work at all. v4.0 fixed the problem from insignia's side. * See bug #463795. */ const int byte_per_sec = 512; if (ioctl(dkfd, DIOCREADCAPACITY, &capacity) != 0) { CDDBG(fprintf(stderr, ">>>>cd: set_up_volume_header: DIOCREADCAPACITY failed errno/%d\n", errno)); return 0; } CDDBG(fprintf(stderr, ">>>>cd: set_up_volume_header: capacity/%d\n", capacity)); bzero(&vh, sizeof vh); vh.vh_magic = VHMAGIC; vh.vh_dp.dp_secbytes = byte_per_sec; vh.vh_dp._dp_heads = 1; /* bug #463795 */ vh.vh_dp._dp_sect = 32; /* bug #463795 */ vh.vh_csum = 0; vh.vh_pt[10].pt_nblks = capacity; vh.vh_pt[10].pt_firstlbn = 0; vh.vh_pt[10].pt_type = PTYPE_VOLUME; vh.vh_pt[8].pt_nblks = 64; vh.vh_pt[8].pt_firstlbn = 0; vh.vh_pt[8].pt_type = PTYPE_VOLHDR; vh.vh_csum = -vhchksum(&vh); if (ioctl(dkfd, DIOCSETVH, &vh) != 0) { CDDBG(fprintf(stderr, ">>>>cd: set_up_volume_header: DIOCSETVH failed errno/%d\n", errno)); return 0; } return 1; } /* * The cache: * * We cache num_blocks blocks. This number is set by the user. * For every block we read, we store its contents in * cache[block % num_blocks].block. */ /* * static int * read_from_cache( CDROM *cd, int start_block, int num_blocks, char *buf ) * * Description: * Read num_blocks blocks from the cache into buf, starting with * start_block. This function only returns success if all blocks * were in the cache; if any block from the range is not in the cache, * all blocks in the range will be read. * * Parameters: * cd * start_block block to start reading from * num_blocks number of blocks to read * buf buffer to receive blocks * * Returns: * 1 if cache hit, 0 if cache miss. */ static int read_from_cache( CDROM *cd, int start_block, int num_blocks, char *buf ) { int i; char *block; CDDBG(fprintf(stderr, ">>>>cd: read_from_cache: start_block/%d, num_blocks/%d, buf/0x%x\n", start_block, num_blocks, buf)); if (!cd->cache_blocks) return (0); for (i = start_block; i - start_block < num_blocks; i++) { block = find_block( cd, i ); if (!block) { CACHEDBG(fprintf(stderr, ">>>>cache: Cache miss( %d, %d )\n", start_block, num_blocks )); return (0); } bcopy( block, buf, cd->blksize ); buf += cd->blksize; } CACHEDBG(fprintf( stderr, ">>>>cache: Cache hit( %d, %d )\n", start_block, num_blocks )); return (1); } /* * static char * * find_block( CDROM *cd, int block ) * * Description: * Look for block in the cache. If it's there, return a pointer to * the data. Otherwise, return 0 * * Parameters: * cd * block block number to look for * * Returns: * pointer to block's data if it's in the cache, 0 otherwise */ static char * find_block( CDROM *cd, int block ) { if (cd->block_num[block % cd->cache_blocks] == block) { CDDBG(fprintf(stderr, ">>>>cd: find_block: block/%d FOUND\n", block)); return (cd->cache + (block % cd->cache_blocks) * cd->blksize); } CDDBG(fprintf(stderr, ">>>>cd: find_block: block/%d !FOUND\n", block)); return (0); } /* * static void * write_to_cache( CDROM *cd, int start_block, int num_blocks, char *buf ) * * Description: * Write num_blocks from buf into the cache * * Parameters: * cd * start_block first block to write * num_blocks number of blocks to write * buf buffer containing data to write to the cache */ static void write_to_cache( CDROM *cd, int start_block, int num_blocks, char *buf ) { int i; CDDBG(fprintf(stderr, ">>>>cd: write_to_cache: start_block/%d, num_blocks/%d, buf/0x%x\n", start_block, num_blocks, buf)); if (!cd->cache_blocks) return; for (i = start_block; i - start_block < num_blocks; i++) { insert_block( cd, i, buf ); buf += cd->blksize; } } /* * static void * insert_block( CDROM *cd, int block, char *buf ) * * Description: * Insert a single block into the cache * * Parameters: * cd * block block to insert * buf buffer containing data for the cache */ static void insert_block( CDROM *cd, int block, char *buf ) { CDDBG(fprintf(stderr, ">>>>cd: insert_block: _block/%d, buf/0x%x\n", block, buf)); if (!cd->cache_blocks) return; CACHEDBG( int slot = block % cd->cache_blocks; if (cd->block_num[slot]) { fprintf(stderr, ">>>>cache: Replacing block %d with block %d\n", cd->block_num[slot], block); } ) cd->block_num[block % cd->cache_blocks] = block; bcopy(buf, cd->cache + (block % cd->cache_blocks) * cd->blksize, cd->blksize); } /* * static int act_like_a_cdrom( CDROM *cd, struct dsreq *dsp ) * * Description: * This command, which must be called after a SCSI reset occurs, * instructs the CD-ROM drive to quit acting like a disk drive and * start acting like a CD-ROM drive. * * Parameters: * dsp devscsi */ static void act_like_a_cdrom( CDROM *cd, struct dsreq *dsp ) { if (!really_is_CDROM) { return; } CDDBG(fprintf(stderr, ">>>>cd: act_like_a_cdrom:\n")); /* * devices other than these 4 (and possibly some later SONY drives?) * may do "bad" things and/or timeout on a c9 command, so only send * it to drives that we think know what to do with it; see bug #314535. */ if (cd->type == TOSHIBA || cd->type == TOSHIBA_SCSI2 || cd->type == TOSHIBA_PHOTO) { fillg1cmd( dsp, (uchar_t *)CMDBUF(dsp), 0xc9, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); filldsreq( dsp, NULL, 0, DSRQ_READ|DSRQ_SENSE ); doscsireq( getfd( dsp ), dsp ); } } /* * int * prevent_removal( struct dsreq *dsp ) * * Description: * Disable the eject button on the CD ROM drive * * Parameters: * dsp devscsi * * Returns: * Status information is in STATUS(dsp) */ static void prevent_removal( struct dsreq *dsp ) { if (!really_is_CDROM) { return; } CDDBG(fprintf(stderr, ">>>>cd: prevent_removal: entered\n")); fillg0cmd( dsp, (uchar_t *)CMDBUF( dsp ), 0x1e, 0, 0, 0, 1, 0 ); filldsreq( dsp, NULL, 0, DSRQ_READ|DSRQ_SENSE ); doscsireq( getfd( dsp ), dsp ); } static void allow_removal( struct dsreq *dsp ) { if (!really_is_CDROM) { return; } CDDBG(fprintf(stderr, ">>>>cd: allow_removal:\n")); fillg0cmd( dsp, (uchar_t *)CMDBUF( dsp ), 0x1e, 0, 0, 0, 0, 0 ); filldsreq( dsp, NULL, 0, DSRQ_READ|DSRQ_SENSE ); doscsireq( getfd( dsp ), dsp ); } /* * len is length of additional data plus 4 byte sense header * plus 8 byte block descriptor + 2 byte page header */ #define SENSE_LEN_ADD (8+4+2) static scsi_modesense(CDROM *cd, sndata_t *addr, u_char pgcode) { struct dk_ioctl_data modesense_data; CDDBG(fprintf(stderr, ">>>>cd: scsi_modesense: control/0x%x, pgcode/0x%x\n", (pgcode & ~ALL) >> 6, pgcode & ALL)); modesense_data.i_page = pgcode; /* before AND ! */ pgcode &= ALL; if ( ! cd->pglengths[pgcode]) { CDDBG(fprintf(stderr, ">>>>cd: page 0x%xd not supported for MODE SENSE\n", pgcode)); return 1; } modesense_data.i_addr = (caddr_t)addr; /* * len is length of additional data plus 4 byte sense header * plus 8 byte block descriptor + 2 byte page header */ modesense_data.i_len = cd->pglengths[pgcode] + SENSE_LEN_ADD; /* only one byte! (don't want modulo) */ if (modesense_data.i_len > 0xff) modesense_data.i_len = 0xff; if (ioctl(cd->dkfd, DIOCSENSE, &modesense_data) < 0) { /* * don't complain if ALL, caller gives slightly different msg */ if (pgcode != ALL) fprintf(stderr, "Warning: problem reading drive parameters (page %d)", pgcode); /* * else get 'better' message in scsi_getsupported() */ return 1; } return 0; } /* * find supported pages and lengths */ static void scsi_getsupported(CDROM *cd) { register i, maxd, pgnum; sndata_t data; u_char *d = (u_char *)&data; CDDBG(fprintf(stderr, ">>>>cd: scsi_getsupported:\n")); bzero(&data, sizeof(data)); /* in case not first call */ bzero(cd->pglengths, sizeof(cd->pglengths)); cd->pglengths[ALL] = sizeof(data) - (1 + SENSE_LEN_ADD); /* * some devices, like maxtor optical won't return default if no media * in drive (or unformatted), but will return current... This is * supposed to be fixed in new firmware soon, but it doesn't cost * anything but a kernel error message to try both. */ if ( scsi_modesense(cd, &data, ALL|CURRENT) && scsi_modesense(cd, &data, ALL|DEFAULT) ) { cd->pglengths[ALL] = 0; fprintf(stderr, "Unable to get list of supported configuration pages"); return; } /* * sense_len doesn't include itself; * set cd->pglengths[ALL] for completeness */ cd->pglengths[ALL] = maxd = data.sense_len + 1; if (data.bd_len > 7) i = 4 + data.bd_len; /* skip header and block desc. */ else i = 4; /* skip just the header */ while (i < maxd) { pgnum = d[i] & ALL; cd->pglengths[pgnum] = d[i+1]; i += cd->pglengths[pgnum] + 2; /* +2 for header */ } }