#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DeviceInfo.H" #include "Device.H" #include "DSReq.H" #include "Log.H" #include "PartitionAddress.H" #include "SCSIAddress.H" class CDROMDevice : public Device { public: CDROMDevice(const DeviceInfo&); const char *short_name() const { return "CDROM"; } const char *full_name() const { return "CD-ROM"; } const char *ftr_name() const { return "cdrom"; } const char *dev_name(FormatIndex, int partno) const; // Device capabilities virtual int features(); // Media presence support virtual int is_media_present(); virtual int eject(); virtual int lock_media(); virtual int unlock_media(); // Media access int capacity(); int sector_size(FormatIndex); bool is_write_protected() { return true; } bool has_audio_data(); int read_data(char *, __uint64_t, unsigned, unsigned); private: // Device-specific constants enum Brand { GENERIC, TOSHIBA, TOSHIBA2, TOSHIBA5401, SONY, MATSHITA }; enum { DATA_SECTOR_SIZE = 512 }; enum { ISO_SECTOR_SIZE = 2048 }; enum { AUDIO_SECTOR_SIZE = 2048 }; enum { G6_READINFO = 0xc7 }; // Toshiba READ TRACK INFO cmd byte // Device-specific method void act_like_a_cdrom(); void get_blksize(); int set_blksize(int); Brand _brand; int _blksize; DSReq _dsr; }; const unsigned int STA_RETRY = ~ (unsigned int) 0; ////////////////////////////////////////////////////////////////////////////// CDROMDevice::CDROMDevice(const DeviceInfo& info) : Device(info), _brand(GENERIC), _blksize(0), _dsr(*info.address().as_SCSIAddress()) { const char Toshiba[] = "TOSHIBA CD"; const char Toshiba5401[] = "TOSHIBA CD-ROM XM-5401"; const char Sony[] = "SONY CD"; const char Matshita[] = "MATSHITACD"; const char *inquiry = info.inquiry(); const char *vendor = inquiry + 8; if (!strncasecmp(vendor, Toshiba, strlen(Toshiba))) { if ((inquiry[2] & 0x07) != 2) _brand = TOSHIBA; else if (!strncasecmp(vendor, Toshiba5401, strlen(Toshiba5401))) _brand = TOSHIBA5401; else _brand = TOSHIBA2; } else if (!strncasecmp(vendor, Sony, strlen(Sony))) _brand = SONY; else if (!strncasecmp(vendor, Matshita, strlen(Matshita))) _brand = MATSHITA; get_blksize(); } // CD-ROM's device name is complicated. EFS and XFS use the // dksc block device driver (/dev/dsk/dksXdYsZ); HFS and ISO 9660 // use the dksc raw driver (/dev/rdsk/dksXdYsZ); others (audio) // use the scsi driver (/dev/scsi/scXdYlZ). const char * CDROMDevice::dev_name(FormatIndex fi, int partno) const { static char path[128]; const SCSIAddress *sa = address().as_SCSIAddress(); if (fi == FMT_EFS || fi == FMT_XFS) { assert(partno != PartitionAddress::WholeDisk); if (partno == 8) (void) sprintf(path, "/dev/dsk/dks%ud%uvh", sa->ctlr(), sa->id()); else (void) sprintf(path, "/dev/dsk/dks%ud%us%u", sa->ctlr(), sa->id(), partno); return path; } else if (fi == FMT_HFS || fi == FMT_ISO) (void) sprintf(path, "/dev/rdsk/dks%ud%uvol", sa->ctlr(), sa->id()); else (void) sprintf(path, "/dev/scsi/sc%ud%ul%u", sa->ctlr(), sa->id(), sa->lun()); return path; } int CDROMDevice::features() { return feature_set(FILESYS_ISO9660 | FILESYS_CDDA | FEATURE_RDONLY | FEATURE_EMPTY_EJECTABLE, FILESYS_DOS); } int CDROMDevice::is_media_present() { dsreq *dsp = _dsr; if (!dsp) return -1; act_like_a_cdrom(); testunitready00(dsp); return STATUS(dsp) == STA_GOOD; } int CDROMDevice::eject() { // Always allow eject -- it's handy to be able to eject an empty drawer. // if (!is_media_present()) // return -1; int error = 0; switch (_brand) { case TOSHIBA: case TOSHIBA2: case TOSHIBA5401: error = _dsr.g1cmd(0xc4, 1, 0, 0, 0, 0, 0, 0, 0, 0); break; default: error = _dsr.g0cmd(0x1b, 1, 0, 0, 2, 0); break; } if (error) { return RMED_ECANTEJECT; } else { return RMED_NOERROR; } } int CDROMDevice::lock_media() { return _dsr.g0cmd(0x1e, 0, 0, 0, 1, 0); } int CDROMDevice::unlock_media() { return _dsr.g0cmd(0x1e, 0, 0, 0, 0, 0); } int CDROMDevice::capacity() { dsreq *dsp = _dsr; unsigned char data[8]; bzero(data, sizeof data); int rc = readcapacity25(dsp, (caddr_t) data, sizeof data, 0, 0, 0); if (rc != STA_GOOD) return rc; return V4(data); } int CDROMDevice::sector_size(FormatIndex index) { if (index == FMT_ISO) return ISO_SECTOR_SIZE; else if (index == FMT_CDDA) return AUDIO_SECTOR_SIZE; else return DATA_SECTOR_SIZE; } bool CDROMDevice::has_audio_data() { const uchar_t S2_TOC = 0x43; // SCSI 2 TOC command dsreq *dsp = _dsr; switch (_brand) { case TOSHIBA: { #define INFO_SIZE 4 uchar_t *info = (uchar_t *)alloca(INFO_SIZE); fillg1cmd(dsp, (uchar_t *) CMDBUF(dsp), G6_READINFO, 0, 0, 0, 0, 0, 0, B2(sizeof info), 0); filldsreq(dsp, info, INFO_SIZE, DSRQ_READ | DSRQ_SENSE); if (doscsireq(getfd(dsp), dsp) != STA_GOOD) return false; fillg1cmd(dsp, (uchar_t *) CMDBUF(dsp), G6_READINFO, 2, info[0], 0, 0, 0, 0, B2(INFO_SIZE), 0); filldsreq(dsp, info, INFO_SIZE, DSRQ_READ | DSRQ_SENSE); if (doscsireq(getfd(dsp), dsp) != STA_GOOD) return false; return (info[3] & 0x4) == 0; } default: // SONY, MATSHITA, GENERIC and newer TOSHIBA /* * Stolen from cdplayer in libcdaudio to make SONY CDROMs * detect music CDs correctly. */ struct toc_header { unsigned short length; unsigned char first; unsigned char last; }; struct toc_entry { unsigned char reserved; unsigned char control; unsigned char track; unsigned char reserved2; unsigned char reserved3; unsigned char min; unsigned char sec; unsigned char frame; }; struct toc { struct toc_header head; struct toc_entry tracks[1]; }; toc track_info; bzero(&track_info, sizeof track_info); fillg1cmd(dsp, (uchar_t *) CMDBUF(dsp), S2_TOC, 2, 0, 0, 0, 0, 0, B2(sizeof track_info), 0); filldsreq(dsp, (uchar_t *) &track_info, sizeof track_info, DSRQ_SENSE | DSRQ_READ); if (doscsireq(getfd(dsp), dsp) != STA_GOOD) return false; return (track_info.tracks[0].control & 0x4) == 0; } } int CDROMDevice::read_data(char *buffer, __uint64_t start_sector, unsigned nsectors, unsigned sectorsize) { assert(start_sector < 1LL << 32); assert(nsectors < 1 << 16); act_like_a_cdrom(); set_blksize(sectorsize); dsreq *dsp = _dsr; if (!dsp) return -1; bzero(buffer, nsectors * sectorsize); fillg1cmd(dsp, (uchar_t *) CMDBUF(dsp), G1_READ, 0, B4(start_sector), 0, B2(nsectors), 0); filldsreq(dsp, (uchar_t *) buffer, nsectors * sectorsize, DSRQ_READ | DSRQ_SENSE); dsp->ds_time = 60 * 1000; // 60 seconds int status = doscsireq(getfd(dsp), dsp); // Set the sector size back to 512 -- other programs expect to find // that size. (specifically, the dksc driver) set_blksize(DATA_SECTOR_SIZE); return status; } ////////////////////////////////////////////////////////////////////////////// void CDROMDevice::act_like_a_cdrom() { if (_brand == TOSHIBA || _brand == TOSHIBA2 || _brand == TOSHIBA5401) (void) _dsr.g1cmd(0xc9, 0, 0, 0, 0, 0, 0, 0, 0, 0); } void CDROMDevice::get_blksize() { #define PARAMS_SIZE (12 + 8) dsreq *dsp = _dsr; uchar_t *params = (uchar_t *)alloca(PARAMS_SIZE); for (int retry = 0; retry < 10; retry++) { switch (_brand) { case TOSHIBA: modesense1a(dsp, (caddr_t) params, 12, 0, 0, 0); break; default: // Amazingly enough, modesense fails if you don't request // any page code information fillg0cmd(dsp, (uchar_t *) CMDBUF(dsp), G0_MSEN, 0, 1, 0, PARAMS_SIZE, 0); filldsreq(dsp, params, PARAMS_SIZE, DSRQ_READ | DSRQ_SENSE); (void) doscsireq(getfd(dsp), dsp); break; } if (STATUS(dsp) == STA_GOOD) { _blksize = V4(params + 8); return; } else if (STATUS(dsp) == STA_CHECK) continue; else if (STATUS(dsp) == STA_RETRY) { sleep(1); continue; } else { _blksize = -1; return; } } } int CDROMDevice::set_blksize(int new_size) { if (_blksize != new_size) { const SCSIAddress *sa = address().as_SCSIAddress(); assert(sa != NULL); char dksc_path[PATH_MAX]; sprintf(dksc_path, "/dev/rdsk/dks%dd%dvol", sa->ctlr(), sa->id()); int dkfd = open(dksc_path, O_RDWR); if (dkfd < 0) { Log::perror("can't open CD-ROM \"%s\"", dksc_path); return -1; } if (ioctl(dkfd, DIOCSELFLAGS, 0x10) != 0) { Log::perror("DIOCSELFLAGS failed on CD-ROM %s", dksc_path); close(dkfd); return -1; } uchar_t params[12]; bzero(params, sizeof params); params[3] = 0x08; MSB4(params + 8, new_size); dk_ioctl_data arg = { (caddr_t) ¶ms, sizeof params, 0 }; if (ioctl(dkfd, DIOCSELECT, &arg) != 0) { Log::perror("DIOCSELECT failed on CD-ROM %s", dksc_path); close(dkfd); return -1; } _blksize = new_size; close(dkfd); } return 1; } ////////////////////////////////////////////////////////////////////////////// extern "C" Device * instantiate(const DeviceInfo& info) { const inventory_s& inv = info.inventory(); if (inv.inv_class != INV_SCSI || inv.inv_type != INV_CDROM) return NULL; if (!info.address().as_SCSIAddress()) return NULL; if (!info.inquiry()) return NULL; return new CDROMDevice(info); }