1
0
Files
irix-657m-src/eoe/cmd/mediad/dev_cdrom.C
2022-09-29 17:59:04 +03:00

415 lines
9.7 KiB
C

#include <assert.h>
#include <bstring.h>
#include <dslib.h>
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/scsi.h>
#include <sys/buf.h>
#include <sys/dvh.h>
#include <sys/iobuf.h>
#include <sys/dksc.h>
#include <sys/dkio.h>
#include <unistd.h>
#include <alloca.h>
#include <mediad.h>
#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) &params, 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);
}