00001 #include "mythcdrom.h"
00002 #include "mythcdrom-linux.h"
00003 #include <sys/ioctl.h>
00004 #include <linux/cdrom.h>
00005 #include <scsi/sg.h>
00006 #include <fcntl.h>
00007 #include <errno.h>
00008 #include <stdint.h>
00009
00010 #include "mythconfig.h"
00011 #include "mythcontext.h"
00012
00013 #include <linux/iso_fs.h>
00014 #include <unistd.h>
00015
00016 #define LOC QString("MythCDROMLinux:")
00017 #define LOC_ERR QString("mythcdrom-linux, Error: ")
00018
00019
00020 #define ASSUME_WANT_AUDIO 1
00021
00022
00023 #define EXTRA_VERBOSITY 1
00024
00025
00026
00027
00028
00029 typedef struct cdrom_generic_command CDROMgenericCmd;
00030
00031
00032 struct event_header
00033 {
00034 unsigned char data_len[2];
00035 #ifdef WORDS_BIGENDIAN
00036 unsigned char nea : 1;
00037 unsigned char reserved1 : 4;
00038 unsigned char notification_class : 3;
00039 #else
00040 unsigned char notification_class : 3;
00041 unsigned char reserved1 : 4;
00042 unsigned char nea : 1;
00043 #endif
00044 unsigned char supp_event_class;
00045 };
00046
00047 struct media_event_desc
00048 {
00049 #ifdef WORDS_BIGENDIAN
00050 unsigned char reserved1 : 4;
00051 unsigned char media_event_code : 4;
00052 unsigned char reserved2 : 6;
00053 unsigned char media_present : 1;
00054 unsigned char door_open : 1;
00055 #else
00056 unsigned char media_event_code : 4;
00057 unsigned char reserved1 : 4;
00058 unsigned char door_open : 1;
00059 unsigned char media_present : 1;
00060 unsigned char reserved2 : 6;
00061 #endif
00062 unsigned char start_slot;
00063 unsigned char end_slot;
00064 };
00065
00066
00067 typedef struct {
00068 uint16_t disc_information_length;
00069 #ifdef WORDS_BIGENDIAN
00070 uint8_t reserved1 : 3;
00071 uint8_t erasable : 1;
00072 uint8_t border_status : 2;
00073 uint8_t disc_status : 2;
00074 #else
00075 uint8_t disc_status : 2;
00076 uint8_t border_status : 2;
00077 uint8_t erasable : 1;
00078 uint8_t reserved1 : 3;
00079 #endif
00080 uint8_t n_first_track;
00081 uint8_t n_sessions_lsb;
00082 uint8_t first_track_lsb;
00083 uint8_t last_track_lsb;
00084 #ifdef WORDS_BIGENDIAN
00085 uint8_t did_v : 1;
00086 uint8_t dbc_v : 1;
00087 uint8_t uru : 1;
00088 uint8_t reserved2 : 5;
00089 #else
00090 uint8_t reserved2 : 5;
00091 uint8_t uru : 1;
00092 uint8_t dbc_v : 1;
00093 uint8_t did_v : 1;
00094 #endif
00095 uint8_t disc_type;
00096 uint8_t n_sessions_msb;
00097 uint8_t first_track_msb;
00098 uint8_t last_track_msb;
00099 uint32_t disc_id;
00100 uint32_t lead_in;
00101 uint32_t lead_out;
00102 uint8_t disc_bar_code[8];
00103 uint8_t reserved3;
00104 uint8_t n_opc;
00105 } CDROMdiscInfo;
00106
00107 enum CDROMdiscStatus
00108 {
00109 MEDIA_IS_EMPTY = 0x0,
00110 MEDIA_IS_APPENDABLE = 0x1,
00111 MEDIA_IS_COMPLETE = 0x2,
00112 MEDIA_IS_OTHER = 0x3
00113 };
00114
00115
00122 class MythCDROMLinux: public MythCDROM
00123 {
00124 public:
00125 MythCDROMLinux(QObject* par, const char* DevicePath, bool SuperMount,
00126 bool AllowEject):
00127 MythCDROM(par, DevicePath, SuperMount, AllowEject) {
00128 }
00129
00130 virtual MediaError testMedia(void);
00131 virtual bool mediaChanged(void);
00132 virtual bool checkOK(void);
00133 virtual MediaStatus checkMedia(void);
00134 virtual MediaError eject(bool open_close = true);
00135 virtual void setSpeed(int speed);
00136 virtual void setSpeed(const char *device, int speed);
00137 virtual bool isSameDevice(const QString &path);
00138 virtual MediaError lock(void);
00139 virtual MediaError unlock(void);
00140
00141 private:
00142 int driveStatus(void);
00143 bool hasWritableMedia(void);
00144 int SCSIstatus(void);
00145 };
00146
00147 MythCDROM *GetMythCDROMLinux(QObject* par, const char* devicePath,
00148 bool SuperMount, bool AllowEject)
00149 {
00150 return new MythCDROMLinux(par, devicePath, SuperMount, AllowEject);
00151 }
00152
00153
00162 int MythCDROMLinux::driveStatus()
00163 {
00164 int drive_status = ioctl(m_DeviceHandle, CDROM_DRIVE_STATUS, CDSL_CURRENT);
00165
00166 if (drive_status == -1)
00167 {
00168 VERBOSE(VB_MEDIA, LOC + ":driveStatus() - ioctl() failed: " + ENO);
00169 return CDS_NO_INFO;
00170 }
00171
00172 if (drive_status == CDS_TRAY_OPEN && m_DevicePath.contains("/dev/scd"))
00173 return SCSIstatus();
00174
00175 return drive_status;
00176 }
00177
00181 bool MythCDROMLinux::hasWritableMedia()
00182 {
00183 unsigned char buffer[32];
00184 CDROMgenericCmd cgc;
00185 CDROMdiscInfo *di;
00186
00187
00188 memset(buffer, 0, sizeof(buffer));
00189 memset(&cgc, 0, sizeof(cgc));
00190
00191 cgc.cmd[0] = GPCMD_READ_DISC_INFO;
00192 cgc.cmd[8] = sizeof(buffer);
00193 cgc.quiet = 1;
00194 cgc.buffer = buffer;
00195 cgc.buflen = sizeof(buffer);
00196 cgc.data_direction = CGC_DATA_READ;
00197
00198 if (ioctl(m_DeviceHandle, CDROM_SEND_PACKET, &cgc) < 0)
00199 {
00200 VERBOSE(VB_MEDIA,
00201 LOC + ":hasWritableMedia() - failed to send packet to "
00202 + m_DevicePath);
00203 return false;
00204 }
00205
00206 di = (CDROMdiscInfo *) buffer;
00207
00208 switch (di->disc_status)
00209 {
00210 case MEDIA_IS_EMPTY:
00211 return true;
00212
00213 case MEDIA_IS_APPENDABLE:
00214
00215
00216
00217 case MEDIA_IS_COMPLETE:
00218 return di->erasable;
00219
00220 case MEDIA_IS_OTHER:
00221 ;
00222 }
00223
00224 return false;
00225 }
00226
00235 int MythCDROMLinux::SCSIstatus()
00236 {
00237 unsigned char buffer[8];
00238 struct cdrom_generic_command cgc;
00239 struct event_header *eh;
00240 struct media_event_desc *med;
00241
00242
00243 memset(buffer, 0, sizeof(buffer));
00244 memset(&cgc, 0, sizeof(cgc));
00245
00246 cgc.cmd[0] = GPCMD_GET_EVENT_STATUS_NOTIFICATION;
00247 cgc.cmd[1] = 1;
00248 cgc.cmd[4] = 1 << 4;
00249 cgc.cmd[8] = sizeof(buffer);
00250 cgc.quiet = 1;
00251 cgc.buffer = buffer;
00252 cgc.buflen = sizeof(buffer);
00253 cgc.data_direction = CGC_DATA_READ;
00254
00255 eh = (struct event_header *) buffer;
00256 med = (struct media_event_desc *) (buffer + sizeof(struct event_header));
00257
00258 if ((ioctl(m_DeviceHandle, CDROM_SEND_PACKET, &cgc) < 0)
00259 || eh->nea || (eh->notification_class != 0x4))
00260 {
00261 VERBOSE(VB_MEDIA,
00262 LOC + ":SCSIstatus() - failed to send SCSI packet to "
00263 + m_DevicePath);
00264 return CDS_TRAY_OPEN;
00265 }
00266
00267 if (med->media_present)
00268 {
00269 #ifdef EXTRA_VERBOSITY
00270 VERBOSE(VB_MEDIA, LOC + ":SCSIstatus() - ioctl() said tray was open,"
00271 "but drive is actually closed with a disc");
00272 #endif
00273 return CDS_DISC_OK;
00274 }
00275 else if (med->door_open)
00276 {
00277 #ifdef EXTRA_VERBOSITY
00278 VERBOSE(VB_MEDIA, LOC + ":SCSIstatus() - tray is definitely open");
00279 #endif
00280 return CDS_TRAY_OPEN;
00281 }
00282
00283 #ifdef EXTRA_VERBOSITY
00284 VERBOSE(VB_MEDIA, LOC + ":SCSIstatus() - ioctl() said tray was open,"
00285 " but drive is actually closed with no disc");
00286 #endif
00287 return CDS_NO_DISC;
00288 }
00289
00290
00291 MediaError MythCDROMLinux::eject(bool open_close)
00292 {
00293 if (!isDeviceOpen())
00294 openDevice();
00295
00296 if (open_close)
00297 return (ioctl(m_DeviceHandle, CDROMEJECT) == 0) ? MEDIAERR_OK
00298 : MEDIAERR_FAILED;
00299 else
00300 {
00301
00302 ioctl(m_DeviceHandle, CDROMCLOSETRAY);
00303
00304
00305
00306 if (driveStatus() == CDS_TRAY_OPEN)
00307 return MEDIAERR_FAILED;
00308 else
00309 return MEDIAERR_OK;
00310 }
00311 }
00312
00313
00314 bool MythCDROMLinux::mediaChanged()
00315 {
00316 return (ioctl(m_DeviceHandle, CDROM_MEDIA_CHANGED, CDSL_CURRENT) > 0);
00317 }
00318
00319 bool MythCDROMLinux::checkOK()
00320 {
00321 return (ioctl(m_DeviceHandle, CDROM_DRIVE_STATUS, CDSL_CURRENT) ==
00322 CDS_DISC_OK);
00323 }
00324
00325
00326 MediaError MythCDROMLinux::testMedia()
00327 {
00328
00329 bool OpenedHere = false;
00330 if (!isDeviceOpen())
00331 {
00332
00333 if (!openDevice())
00334 {
00335 #ifdef EXTRA_VERBOSITY
00336 VERBOSE(VB_MEDIA, LOC + ":testMedia - failed to open "
00337 + m_DevicePath + ENO);
00338 #endif
00339 if (errno == EBUSY)
00340 return isMounted(true) ? MEDIAERR_OK : MEDIAERR_FAILED;
00341 else
00342 return MEDIAERR_FAILED;
00343 }
00344 #ifdef EXTRA_VERBOSITY
00345 VERBOSE(VB_MEDIA, LOC + ":testMedia - Opened device");
00346 #endif
00347 OpenedHere = true;
00348 }
00349
00350
00351 int Stat = driveStatus();
00352
00353
00354
00355 if (OpenedHere)
00356 closeDevice();
00357
00358 if (Stat == -1)
00359 {
00360 VERBOSE(VB_MEDIA, LOC + ":testMedia - Failed to get drive status of '"
00361 + m_DevicePath + "' : " + ENO);
00362 return MEDIAERR_FAILED;
00363 }
00364
00365 return MEDIAERR_OK;
00366 }
00367
00368 MediaStatus MythCDROMLinux::checkMedia()
00369 {
00370 bool OpenedHere = false;
00371
00372
00373
00374 if (!isDeviceOpen())
00375 {
00376 OpenedHere = openDevice();
00377
00378 if (!OpenedHere)
00379 {
00380 VERBOSE(VB_MEDIA, LOC + ":checkMedia() - cannot open device '"
00381 + m_DevicePath + "' : "
00382 + ENO + "- returning UNKNOWN");
00383 m_MediaType = MEDIATYPE_UNKNOWN;
00384 return setStatus(MEDIASTAT_UNKNOWN, false);
00385 }
00386 }
00387
00388 switch (driveStatus())
00389 {
00390 case CDS_DISC_OK:
00391 VERBOSE(VB_MEDIA, m_DevicePath + " Disk OK, type = "
00392 + MediaTypeString(m_MediaType) );
00393
00394 break;
00395 case CDS_TRAY_OPEN:
00396 VERBOSE(VB_MEDIA, m_DevicePath + " Tray open or no disc");
00397
00398
00399 setStatus(MEDIASTAT_OPEN, OpenedHere);
00400
00401 m_MediaType = MEDIATYPE_UNKNOWN;
00402 return MEDIASTAT_OPEN;
00403 break;
00404 case CDS_NO_DISC:
00405 VERBOSE(VB_MEDIA, m_DevicePath + " No disc");
00406 m_MediaType = MEDIATYPE_UNKNOWN;
00407 return setStatus(MEDIASTAT_NODISK, OpenedHere);
00408 break;
00409 case CDS_NO_INFO:
00410 case CDS_DRIVE_NOT_READY:
00411 VERBOSE(VB_MEDIA, m_DevicePath + " No info or drive not ready");
00412 m_MediaType = MEDIATYPE_UNKNOWN;
00413 return setStatus(MEDIASTAT_UNKNOWN, OpenedHere);
00414 default:
00415 VERBOSE(VB_IMPORTANT, "Failed to get drive status of "
00416 + m_DevicePath + " : " + ENO);
00417 m_MediaType = MEDIATYPE_UNKNOWN;
00418 return setStatus(MEDIASTAT_UNKNOWN, OpenedHere);
00419 }
00420
00421 if (mediaChanged())
00422 {
00423 VERBOSE(VB_MEDIA, m_DevicePath + " Media changed");
00424
00425
00426 return setStatus(MEDIASTAT_OPEN, OpenedHere);
00427 }
00428
00429
00430 if (isUsable())
00431 {
00432 #ifdef EXTRA_VERBOSITY
00433 VERBOSE(VB_MEDIA, "Disc useable, media unchanged. All good!");
00434 #endif
00435 if (OpenedHere)
00436 closeDevice();
00437 return MEDIASTAT_USEABLE;
00438 }
00439
00440
00441 if (m_Status == MEDIASTAT_ERROR)
00442 {
00443 #ifdef EXTRA_VERBOSITY
00444 VERBOSE(VB_MEDIA, "Disc is unmountable?");
00445 #endif
00446 if (OpenedHere)
00447 closeDevice();
00448 return m_Status;
00449 }
00450
00451 if ((m_Status == MEDIASTAT_OPEN) ||
00452 (m_Status == MEDIASTAT_UNKNOWN))
00453 {
00454 VERBOSE(VB_MEDIA, m_DevicePath + " Current status " +
00455 MythMediaDevice::MediaStatusStrings[m_Status]);
00456 int type = ioctl(m_DeviceHandle, CDROM_DISC_STATUS, CDSL_CURRENT);
00457 switch (type)
00458 {
00459 case CDS_DATA_1:
00460 case CDS_DATA_2:
00461 m_MediaType = MEDIATYPE_DATA;
00462 VERBOSE(VB_MEDIA, "Found a data disk");
00463
00464 struct iso_primary_descriptor buf;
00465 lseek(this->m_DeviceHandle,(off_t) 2048*16,SEEK_SET);
00466 read(this->m_DeviceHandle, &buf,2048);
00467 this->m_VolumeID = QString(buf.volume_id).stripWhiteSpace();
00468 this->m_KeyID = QString("%1%2").arg(this->m_VolumeID)
00469 .arg(QString(buf.creation_date).left(16));
00470
00471
00472
00473 if (isMounted(true))
00474 onDeviceMounted();
00475 else
00476 if (mount())
00477 ;
00478 else
00479 return setStatus(MEDIASTAT_ERROR, OpenedHere);
00480
00481 if (isMounted(true))
00482 {
00483
00484 m_Status = MEDIASTAT_NOTMOUNTED;
00485 return setStatus(MEDIASTAT_MOUNTED, OpenedHere);
00486 }
00487 else if (m_MediaType == MEDIATYPE_DVD)
00488 {
00489
00490 m_Status = MEDIASTAT_NOTMOUNTED;
00491 return setStatus(MEDIASTAT_USEABLE, OpenedHere);
00492 }
00493 else
00494 return setStatus(MEDIASTAT_NOTMOUNTED, OpenedHere);
00495 break;
00496 case CDS_AUDIO:
00497 VERBOSE(VB_MEDIA, "found an audio disk");
00498 m_MediaType = MEDIATYPE_AUDIO;
00499 return setStatus(MEDIASTAT_USEABLE, OpenedHere);
00500 break;
00501 case CDS_MIXED:
00502 m_MediaType = MEDIATYPE_MIXED;
00503 VERBOSE(VB_MEDIA, "found a mixed CD");
00504
00505
00506
00507 #ifdef ASSUME_WANT_AUDIO
00508 return setStatus(MEDIASTAT_USEABLE, OpenedHere);
00509 #else
00510 mount();
00511 if (isMounted(true))
00512 {
00513
00514
00515 m_Status = MEDIASTAT_NOTMOUNTED;
00516 return setStatus(MEDIASTAT_MOUNTED, OpenedHere);
00517 }
00518 else
00519 {
00520 return setStatus(MEDIASTAT_USEABLE, OpenedHere);
00521 }
00522 #endif
00523 break;
00524 case CDS_NO_INFO:
00525 case CDS_NO_DISC:
00526 if (hasWritableMedia())
00527 {
00528 VERBOSE(VB_MEDIA, "found a blank or writable disk");
00529 return setStatus(MEDIASTAT_UNFORMATTED, OpenedHere);
00530 }
00531
00532 VERBOSE(VB_MEDIA, "found no disk");
00533 m_MediaType = MEDIATYPE_UNKNOWN;
00534 return setStatus(MEDIASTAT_UNKNOWN, OpenedHere);
00535 break;
00536 default:
00537 VERBOSE(VB_MEDIA, "found unknown disk type: "
00538 + QString().setNum(type));
00539 m_MediaType = MEDIATYPE_UNKNOWN;
00540 return setStatus(MEDIASTAT_UNKNOWN, OpenedHere);
00541 }
00542 }
00543
00544 if (m_AllowEject)
00545 unlock();
00546 else
00547 lock();
00548
00549 if (OpenedHere)
00550 closeDevice();
00551
00552 #ifdef EXTRA_VERBOSITY
00553 VERBOSE(VB_MEDIA, QString("Returning ")
00554 + MythMediaDevice::MediaStatusStrings[m_Status]);
00555 #endif
00556 return m_Status;
00557 }
00558
00559 MediaError MythCDROMLinux::lock()
00560 {
00561 MediaError ret = MythMediaDevice::lock();
00562 if (ret == MEDIAERR_OK)
00563 ioctl(m_DeviceHandle, CDROM_LOCKDOOR, 1);
00564
00565 return ret;
00566 }
00567
00568 MediaError MythCDROMLinux::unlock()
00569 {
00570 if (isDeviceOpen() || openDevice())
00571 {
00572 #ifdef EXTRA_VERBOSITY
00573 VERBOSE(VB_MEDIA, LOC + ":unlock - Unlocking CDROM door");
00574 #endif
00575 ioctl(m_DeviceHandle, CDROM_LOCKDOOR, 0);
00576 }
00577 else
00578 {
00579 VERBOSE(VB_GENERAL, "Failed to open device, CDROM try will remain "
00580 "locked.");
00581 }
00582
00583 return MythMediaDevice::unlock();
00584 }
00585
00586 bool MythCDROMLinux::isSameDevice(const QString &path)
00587 {
00588 dev_t new_rdev;
00589 struct stat sb;
00590
00591 if (stat(path, &sb) < 0)
00592 {
00593 VERBOSE(VB_IMPORTANT, LOC + ":isSameDevice() -- " +
00594 QString("Failed to stat '%1'")
00595 .arg(path) + ENO);
00596 return false;
00597 }
00598 new_rdev = sb.st_rdev;
00599
00600
00601 if (stat(m_DevicePath, &sb) < 0)
00602 {
00603 VERBOSE(VB_IMPORTANT, LOC + ":isSameDevice() -- " +
00604 QString("Failed to stat '%1'")
00605 .arg(m_DevicePath) + ENO);
00606 return false;
00607 }
00608 return (sb.st_rdev == new_rdev);
00609 }
00610
00611 #if defined(SG_IO) && defined(GPCMD_SET_STREAMING)
00612
00613
00614
00615 void MythCDROMLinux::setSpeed(int speed)
00616 {
00617 MythCDROMLinux::setSpeed(m_DevicePath, speed);
00618 }
00619
00620 void MythCDROMLinux::setSpeed(const char *device, int speed)
00621 {
00622 int fd;
00623 unsigned char buffer[28];
00624 unsigned char cmd[16];
00625 unsigned char sense[16];
00626 struct sg_io_hdr sghdr;
00627 struct stat st;
00628 int rate = 0;
00629
00630 memset(&sghdr, 0, sizeof(sghdr));
00631 memset(buffer, 0, sizeof(buffer));
00632 memset(sense, 0, sizeof(sense));
00633 memset(cmd, 0, sizeof(cmd));
00634 memset(&st, 0, sizeof(st));
00635
00636 if (stat(device, &st) == -1)
00637 {
00638 VERBOSE(VB_MEDIA, LOC_ERR +
00639 QString("setSpeed() Failed. device %1 not found")
00640 .arg(device));
00641 return;
00642 }
00643
00644 if (!S_ISBLK(st.st_mode))
00645 {
00646 VERBOSE(VB_MEDIA, LOC_ERR + "setSpeed() Failed. Not a block device");
00647 return;
00648 }
00649
00650 if ((fd = open(device, O_RDWR | O_NONBLOCK)) == -1)
00651 {
00652 VERBOSE(VB_MEDIA, LOC_ERR + "Changing CD/DVD speed needs write access");
00653 return;
00654 }
00655
00656 if (speed < 0)
00657 speed = -1;
00658
00659 switch(speed)
00660 {
00661 case 0:
00662 return;
00663 case -1:
00664 {
00665 rate = 0;
00666 buffer[0] = 4;
00667 VERBOSE(VB_MEDIA, LOC + ":setSpeed() - Restored CD/DVD Speed");
00668 break;
00669 }
00670 default:
00671 {
00672
00673
00674
00675 rate = (speed > 0 && speed < 100) ? speed * 177 : speed;
00676
00677 VERBOSE(VB_MEDIA,
00678 (LOC + ":setSpeed() - Limiting CD/DVD Speed to %1KB/s")
00679 .arg(rate));
00680 break;
00681 }
00682 }
00683
00684 sghdr.interface_id = 'S';
00685 sghdr.timeout = 5000;
00686 sghdr.dxfer_direction = SG_DXFER_TO_DEV;
00687 sghdr.mx_sb_len = sizeof(sense);
00688 sghdr.dxfer_len = sizeof(buffer);
00689 sghdr.cmd_len = sizeof(cmd);
00690 sghdr.sbp = sense;
00691 sghdr.dxferp = buffer;
00692 sghdr.cmdp = cmd;
00693
00694 cmd[0] = GPCMD_SET_STREAMING;
00695 cmd[10] = sizeof(buffer);
00696
00697 buffer[8] = 0xff;
00698 buffer[9] = 0xff;
00699 buffer[10] = 0xff;
00700 buffer[11] = 0xff;
00701
00702 buffer[12] = buffer[20] = (rate >> 24) & 0xff;
00703 buffer[13] = buffer[21] = (rate >> 16) & 0xff;
00704 buffer[14] = buffer[22] = (rate >> 8) & 0xff;
00705 buffer[15] = buffer[23] = rate & 0xff;
00706
00707
00708 buffer[18] = buffer[26] = 0x03;
00709 buffer[19] = buffer[27] = 0xe8;
00710
00711 if (ioctl(fd, SG_IO, &sghdr) < 0)
00712 {
00713 VERBOSE(VB_MEDIA, LOC_ERR + "Limit CD/DVD Speed Failed");
00714 }
00715 else
00716 {
00717
00718
00719 if (ioctl(fd, CDROM_SELECT_SPEED, speed) < 0)
00720 {
00721 VERBOSE(VB_MEDIA, LOC_ERR +
00722 "Limit CD/DVD CDROM_SELECT_SPEED Failed");
00723 }
00724 VERBOSE(VB_MEDIA, LOC + ":setSpeed() - CD/DVD Speed Set Successful");
00725 }
00726
00727 close(fd);
00728 }
00729 #endif