Merge pull request #6835 from nakioman/linux_ioctl

Linux DVD/CD IOCTL Support
This commit is contained in:
Miran Grča
2026-02-23 01:24:10 +01:00
committed by GitHub
4 changed files with 892 additions and 3 deletions

View File

@@ -348,8 +348,9 @@ endif()
if(WIN32)
target_sources(plat PRIVATE win_cdrom_ioctl.c)
elseif(UNIX AND NOT APPLE)
target_sources(plat PRIVATE ../unix/linux_cdrom_ioctl.c)
else()
# Replace with proper *nix and mac handler files once they are done.
target_sources(plat PRIVATE dummy_cdrom_ioctl.c)
endif()

View File

@@ -20,6 +20,7 @@
#include "qt_machinestatus.hpp"
#include <QMenu>
#include <QFile>
#include <QFileDialog>
#include <QMessageBox>
#include <QStringBuilder>
@@ -176,7 +177,15 @@ MediaMenu::refresh(QMenu *parentMenu)
menu->addAction(QIcon(":/settings/qt/icons/cdrom_host.ico"), tr("&Host CD/DVD Drive (%1:)").arg(letter), [this, i, letter] { cdromMount(i, 2, QString(R"(\\.\%1:)").arg(letter)); })->setCheckable(false);
}
menu->addSeparator();
#endif // Q_OS_WINDOWS
#elif defined(Q_OS_LINUX)
/* Enumerate Linux CD/DVD drives (/dev/sr0 .. /dev/sr15). */
for (int sr = 0; sr < 16; sr++) {
QString devPath = QString("/dev/sr%1").arg(sr);
if (QFile::exists(devPath))
menu->addAction(QIcon(":/settings/qt/icons/cdrom_host.ico"), tr("&Host CD/DVD Drive (%1)").arg(devPath), [this, i, devPath] { cdromMount(i, 2, devPath); })->setCheckable(false);
}
menu->addSeparator();
#endif
cdromEjectPos = menu->children().count();
menu->addAction(tr("E&ject"), [this, i]() { cdromEject(i); })->setCheckable(false);
cdromMenus[i] = menu;

View File

@@ -47,8 +47,12 @@ add_library(ui OBJECT
unix_sdl.c
unix_osd.c
unix_cdrom.c
dummy_cdrom_ioctl.c
)
if(APPLE)
target_sources(ui PRIVATE dummy_cdrom_ioctl.c)
else()
target_sources(ui PRIVATE linux_cdrom_ioctl.c)
endif()
target_compile_definitions(ui PUBLIC _FILE_OFFSET_BITS=64)
target_link_libraries(ui ${CMAKE_DL_LIBS})

View File

@@ -0,0 +1,875 @@
/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* Linux CD-ROM support via IOCTL (SG_IO).
*
* Authors: Miran Grca, <mgrca8@gmail.com>
* TheCollector1995, <mariogplayer@gmail.com>
*
* Copyright 2023 TheCollector1995.
* Copyright 2023 Miran Grca.
* Copyright 2025 86Box contributors.
*/
#include <inttypes.h>
#ifdef ENABLE_IOCTL_LOG
#include <stdarg.h>
#endif
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <scsi/sg.h>
#include <linux/cdrom.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/cdrom.h>
#include <86box/log.h>
#include <86box/plat_cdrom_ioctl.h>
#include <86box/scsi_device.h>
typedef struct ioctl_t {
cdrom_t *dev;
void *log;
int fd;
int is_dvd;
int has_audio;
int blocks_num;
uint8_t cur_rti[65536];
char path[256];
pthread_t poll_tid;
int poll_active;
} ioctl_t;
static int ioctl_read_dvd_structure(const void *local, uint8_t layer, uint8_t format,
uint8_t *buffer, uint32_t *info);
static int ioctl_is_empty(const void *local);
/*
* Wrapper for the system ioctl() call to avoid naming collisions
* with the local 'ioctl' variable of type ioctl_t*.
*/
static inline int
sys_ioctl(int fd, unsigned long request, void *arg)
{
return ioctl(fd, request, arg);
}
#ifdef ENABLE_IOCTL_LOG
int ioctl_do_log = ENABLE_IOCTL_LOG;
void
ioctl_log(void *priv, const char *fmt, ...)
{
if (ioctl_do_log) {
va_list ap;
va_start(ap, fmt);
log_out(priv, fmt, ap);
va_end(ap);
}
}
#else
# define ioctl_log(priv, fmt, ...)
#endif
/* Internal functions. */
static void
ioctl_close_handle(const ioctl_t *ioctl)
{
if (ioctl->fd >= 0)
close(ioctl->fd);
}
static int
ioctl_open_handle(ioctl_t *ioctl)
{
ioctl_log(ioctl->log, "ioctl->path = \"%s\"\n", ioctl->path);
ioctl->fd = open(ioctl->path, O_RDONLY | O_NONBLOCK);
ioctl_log(ioctl->log, "fd=%d, errno=%d\n", ioctl->fd, errno);
return (ioctl->fd >= 0);
}
/*
* Execute a SCSI command via the Linux SG_IO interface.
* Returns 1 on success, 0 on failure.
* sense_buf should be at least 64 bytes, sense_len receives actual sense length.
*/
static int
sg_io_cmd(int fd, const uint8_t *cdb, int cdb_len,
uint8_t *data_buf, int data_len, int direction,
uint8_t *sense_buf, int *sense_len)
{
sg_io_hdr_t io_hdr;
memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
io_hdr.interface_id = 'S';
io_hdr.cmd_len = cdb_len;
io_hdr.mx_sb_len = 64;
io_hdr.dxfer_direction = direction;
io_hdr.dxfer_len = data_len;
io_hdr.dxferp = data_buf;
io_hdr.cmdp = (unsigned char *) cdb;
io_hdr.sbp = sense_buf;
io_hdr.timeout = 6000; /* 6 seconds, matching Windows */
if (ioctl(fd, SG_IO, &io_hdr) < 0)
return 0;
if (sense_len != NULL)
*sense_len = io_hdr.sb_len_wr;
/* Check for SCSI errors. */
if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
if (io_hdr.sb_len_wr > 0) {
if (sense_len != NULL)
*sense_len = io_hdr.sb_len_wr;
return 0;
}
return 0;
}
return 1;
}
static int
ioctl_read_normal_toc(ioctl_t *ioctl, uint8_t *toc_buf, int32_t *tracks_num)
{
struct cdrom_tochdr tochdr;
struct cdrom_tocentry tocentry;
int status;
*tracks_num = 0;
memset(toc_buf, 0x00, 65536);
status = sys_ioctl(ioctl->fd, CDROMREADTOCHDR, &tochdr);
if (status < 0)
return 0;
ioctl_log(ioctl->log, "TOC: first=%d, last=%d\n",
tochdr.cdth_trk0, tochdr.cdth_trk1);
/*
* Build a cooked TOC buffer in the same format as the Windows
* CDROM_TOC structure:
* [0..1] = length (big-endian)
* [2] = first track
* [3] = last track
* [4..] = 8-byte track descriptors
*
* Each track descriptor:
* [0] = reserved
* [1] = Adr/Control
* [2] = track number
* [3] = reserved
* [4..7] = MSF address (0, M, S, F)
*/
toc_buf[2] = tochdr.cdth_trk0;
toc_buf[3] = tochdr.cdth_trk1;
int count = 0;
for (int i = tochdr.cdth_trk0; i <= tochdr.cdth_trk1; i++) {
memset(&tocentry, 0, sizeof(tocentry));
tocentry.cdte_track = i;
tocentry.cdte_format = CDROM_MSF;
if (sys_ioctl(ioctl->fd, CDROMREADTOCENTRY, &tocentry) < 0)
continue;
uint8_t *t = &toc_buf[4 + count * 8];
t[0] = 0;
t[1] = ((tocentry.cdte_adr & 0xf) << 4) | (tocentry.cdte_ctrl & 0xf);
t[2] = i;
t[3] = 0;
t[4] = 0;
t[5] = tocentry.cdte_addr.msf.minute;
t[6] = tocentry.cdte_addr.msf.second;
t[7] = tocentry.cdte_addr.msf.frame;
count++;
}
/* Lead-out (track 0xAA). */
memset(&tocentry, 0, sizeof(tocentry));
tocentry.cdte_track = CDROM_LEADOUT;
tocentry.cdte_format = CDROM_MSF;
if (sys_ioctl(ioctl->fd, CDROMREADTOCENTRY, &tocentry) >= 0) {
uint8_t *t = &toc_buf[4 + count * 8];
t[0] = 0;
t[1] = ((tocentry.cdte_adr & 0xf) << 4) | (tocentry.cdte_ctrl & 0xf);
t[2] = 0xAA;
t[3] = 0;
t[4] = 0;
t[5] = tocentry.cdte_addr.msf.minute;
t[6] = tocentry.cdte_addr.msf.second;
t[7] = tocentry.cdte_addr.msf.frame;
count++;
}
/* Set the length field (big-endian). */
int length = 2 + count * 8;
toc_buf[0] = (length >> 8) & 0xff;
toc_buf[1] = length & 0xff;
*tracks_num = count;
ioctl_log(ioctl->log, "%i tracks\n", *tracks_num);
return 1;
}
static void
ioctl_read_raw_toc(ioctl_t *ioctl)
{
raw_track_info_t *rti = (raw_track_info_t *) ioctl->cur_rti;
uint8_t *buffer = (uint8_t *) calloc(1, 2052);
int status = 0;
ioctl->is_dvd = (ioctl_read_dvd_structure(ioctl, 0, 0, buffer, NULL) > 0);
free(buffer);
ioctl->has_audio = 0;
ioctl->blocks_num = 0;
memset(ioctl->cur_rti, 0x00, 65536);
if (!ioctl->is_dvd) {
/* Try SG_IO with READ TOC command, Format=2 (full/raw TOC). */
uint8_t cdb[12];
uint8_t sense[64];
uint8_t *raw_buf = (uint8_t *) calloc(1, 65536);
int sense_len = 0;
memset(cdb, 0, sizeof(cdb));
cdb[0] = 0x43; /* READ TOC */
cdb[1] = 0x02; /* MSF */
cdb[2] = 0x02; /* Format = Full TOC (raw) */
cdb[6] = 0x01; /* Session = 1 */
cdb[7] = 0xff; /* Allocation length high */
cdb[8] = 0xff; /* Allocation length low */
memset(sense, 0, sizeof(sense));
status = sg_io_cmd(ioctl->fd, cdb, 10, raw_buf, 65535,
SG_DXFER_FROM_DEV, sense, &sense_len);
if (status && sense_len == 0) {
int length = ((raw_buf[0] << 8) | raw_buf[1]) - 2;
ioctl->blocks_num = length / 11;
if (ioctl->blocks_num > 0)
memcpy(ioctl->cur_rti, &raw_buf[4], ioctl->blocks_num * 11);
} else {
status = 0;
}
free(raw_buf);
}
if (status == 0) {
/*
* Raw TOC read failed (or this is a DVD). Fall back to the
* cooked TOC, and construct raw_track_info_t entries from it,
* mirroring the Windows fallback path.
*/
uint8_t cur_toc[65536] = { 0 };
int32_t tracks_num = 0;
status = ioctl_read_normal_toc(ioctl, cur_toc, &tracks_num);
if ((status > 0) && (tracks_num >= 1)) {
/* Last real entry (the lead-out). */
const uint8_t *ct = &cur_toc[4 + (tracks_num - 1) * 8];
rti[0].adr_ctl = ct[1];
rti[0].point = 0xa0;
rti[0].pm = cur_toc[2]; /* FirstTrack */
rti[1].adr_ctl = rti[0].adr_ctl;
rti[1].point = 0xa1;
rti[1].pm = cur_toc[3]; /* LastTrack */
rti[2].adr_ctl = rti[0].adr_ctl;
rti[2].point = 0xa2;
rti[2].pm = ct[5]; /* M */
rti[2].ps = ct[6]; /* S */
rti[2].pf = ct[7]; /* F */
ioctl->blocks_num = 3;
for (int i = 0; i < (tracks_num - 1); i++) {
raw_track_info_t *crt = &(rti[ioctl->blocks_num]);
ct = &cur_toc[4 + i * 8];
crt->adr_ctl = ct[1];
crt->point = ct[2];
crt->pm = ct[5];
crt->ps = ct[6];
crt->pf = ct[7];
ioctl->blocks_num++;
}
} else if (status > 0)
/* Announce that we've had a failure. */
status = 0;
}
if (ioctl->blocks_num) for (int i = 0; i < ioctl->blocks_num; i++) {
const raw_track_info_t *crt = &(rti[i]);
if ((crt->point >= 1) && (crt->point <= 99) && !(crt->adr_ctl & 0x04)) {
ioctl->has_audio = 1;
break;
}
}
#ifdef ENABLE_IOCTL_LOG
ioctl_log(ioctl->log, "%i blocks\n", ioctl->blocks_num);
for (int i = 0; i < ioctl->blocks_num; i++) {
uint8_t *t = (uint8_t *) &rti[i];
ioctl_log(ioctl->log, "Block %03i: %02X %02X %02X %02X %02X %02X %02X %02X "
"%02X %02X %02X\n",
i, t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8],
t[9], t[10]);
}
#endif
}
static int
ioctl_get_track(const ioctl_t *ioctl, const uint32_t sector)
{
raw_track_info_t *rti = (raw_track_info_t *) ioctl->cur_rti;
int track = -1;
for (int i = (ioctl->blocks_num - 1); i >= 0; i--) {
const raw_track_info_t *ct = &(rti[i]);
const uint32_t start = (ct->pm * 60 * 75) + (ct->ps * 75) + ct->pf - 150;
ioctl_log(ioctl->log, "ioctl_get_track(): ct: %02X, %08X\n",
ct->point, start);
if ((ct->point >= 1) && (ct->point <= 99) && (sector >= start)) {
track = i;
ioctl_log(ioctl->log, "ioctl_get_track(): found track: %i\n", i);
break;
}
}
return track;
}
static int
ioctl_is_track_audio(const ioctl_t *ioctl, const uint32_t pos)
{
const raw_track_info_t *rti = (const raw_track_info_t *) ioctl->cur_rti;
int ret = 0;
if (ioctl->has_audio && !ioctl->is_dvd) {
const int track = ioctl_get_track(ioctl, pos);
const int control = rti[track].adr_ctl;
ret = !(control & 0x04);
ioctl_log(ioctl->log, "ioctl_is_track_audio(%08X, %02X): %i\n", pos, track, ret);
}
return ret;
}
/* Shared functions (cdrom_ops_t interface). */
static int
ioctl_get_track_info(const void *local, const uint32_t track,
int end, track_info_t *ti)
{
const ioctl_t *ioctl = (const ioctl_t *) local;
const raw_track_info_t *rti = (const raw_track_info_t *) ioctl->cur_rti;
int ret = 1;
int trk = -1;
int next = -1;
if ((track >= 1) && (track < 99))
for (int i = 0; i < ioctl->blocks_num; i++)
if (rti[i].point == track) {
trk = i;
break;
}
if ((track >= 1) && (track < 98))
for (int i = 0; i < ioctl->blocks_num; i++)
if ((rti[i].point == (track + 1)) && (rti[i].session == rti[trk].session)) {
next = i;
break;
}
if ((track >= 1) && (track < 99) && (trk != -1) && (next == -1))
for (int i = 0; i < ioctl->blocks_num; i++)
if ((rti[i].point == 0xa2) && (rti[i].session == rti[trk].session)) {
next = i;
break;
}
if ((track == 0xaa) || (trk == -1)) {
ioctl_log(ioctl->log, "ioctl_get_track_info(%02i)\n", track);
ret = 0;
} else {
if (end) {
if (next != -1) {
ti->m = rti[next].pm;
ti->s = rti[next].ps;
ti->f = rti[next].pf;
}
} else {
ti->m = rti[trk].pm;
ti->s = rti[trk].ps;
ti->f = rti[trk].pf;
}
ti->number = rti[trk].point;
ti->attr = rti[trk].adr_ctl;
ioctl_log(ioctl->log, "ioctl_get_track_info(%02i): %02i:%02i:%02i, %02i, %02X\n",
track, ti->m, ti->s, ti->f, ti->number, ti->attr);
}
return ret;
}
static void
ioctl_get_raw_track_info(const void *local, int *num, uint8_t *rti)
{
const ioctl_t *ioctl = (const ioctl_t *) local;
*num = ioctl->blocks_num;
memcpy(rti, ioctl->cur_rti, ioctl->blocks_num * 11);
}
static int
ioctl_is_track_pre(const void *local, const uint32_t sector)
{
const ioctl_t *ioctl = (const ioctl_t *) local;
const raw_track_info_t *rti = (const raw_track_info_t *) ioctl->cur_rti;
int ret = 0;
if (ioctl->has_audio && !ioctl->is_dvd) {
const int track = ioctl_get_track(ioctl, sector);
const int control = rti[track].adr_ctl;
ret = control & 0x01;
ioctl_log(ioctl->log, "ioctl_is_track_pre(%08X, %02X): %i\n", sector, track, ret);
}
return ret;
}
static int
ioctl_read_sector(const void *local, uint8_t *buffer, uint32_t const sector)
{
const ioctl_t *ioctl = (const ioctl_t *) local;
const raw_track_info_t *rti = (raw_track_info_t *) ioctl->cur_rti;
const int sc_offs = (sector == 0xffffffff) ? 0 : 2352;
int len = (sector == 0xffffffff) ? 16 : 2368;
int m = 0;
int s = 0;
int f = 0;
uint32_t lba = sector;
int ret;
int data_len = 0;
if (ioctl->is_dvd) {
int track;
data_len = 0;
ret = 0;
if (lba == 0xffffffff) {
lba = ioctl->dev->seek_pos;
track = ioctl_get_track(ioctl, lba);
if (track != -1) {
data_len = len;
ret = 1;
}
} else {
len = COOKED_SECTOR_SIZE;
track = ioctl_get_track(ioctl, lba);
if (track != -1) {
ssize_t nread = pread(ioctl->fd, &(buffer[16]),
COOKED_SECTOR_SIZE,
(off_t) lba * COOKED_SECTOR_SIZE);
if (nread > 0) {
data_len = (int) nread;
ret = 1;
}
}
}
if (ret && (data_len >= len) && (track != -1)) {
const raw_track_info_t *ct = &(rti[track]);
const uint32_t start = (ct->pm * 60 * 75) + (ct->ps * 75) + ct->pf;
m = s = f = 0;
/* Construct sector header and sub-header. */
if (sector != 0xffffffff) {
/* Sync bytes. */
buffer[0] = 0x00;
memset(&(buffer[1]), 0xff, 10);
buffer[11] = 0x00;
/* Sector header. */
FRAMES_TO_MSF(lba + 150, &m, &s, &f);
buffer[12] = bin2bcd(m);
buffer[13] = bin2bcd(s);
buffer[14] = bin2bcd(f);
/* Mode 1 data. */
buffer[15] = 0x01;
}
/* Construct Q. */
buffer[sc_offs + 0] = (ct->adr_ctl >> 4) | ((ct->adr_ctl & 0xf) << 4);
buffer[sc_offs + 1] = bin2bcd(ct->point);
buffer[sc_offs + 2] = 1;
FRAMES_TO_MSF((int32_t) (lba + 150 - start), &m, &s, &f);
buffer[sc_offs + 3] = bin2bcd(m);
buffer[sc_offs + 4] = bin2bcd(s);
buffer[sc_offs + 5] = bin2bcd(f);
FRAMES_TO_MSF(lba + 150, &m, &s, &f);
buffer[sc_offs + 7] = bin2bcd(m);
buffer[sc_offs + 8] = bin2bcd(s);
buffer[sc_offs + 9] = bin2bcd(f);
}
} else {
/* CD: use SG_IO with READ CD (0xBE). */
uint8_t cdb[12];
uint8_t sense[64];
int sense_len = 0;
memset(cdb, 0, sizeof(cdb));
cdb[0] = 0xbe; /* READ CD */
cdb[1] = 0x00;
cdb[2] = (sector >> 24) & 0xff;
cdb[3] = (sector >> 16) & 0xff;
cdb[4] = (sector >> 8) & 0xff;
cdb[5] = sector & 0xff; /* Starting LBA */
cdb[6] = 0x00;
cdb[7] = 0x00;
cdb[8] = 0x01; /* Transfer Length = 1 */
/* If sector is FFFFFFFF, only return the subchannel. */
cdb[9] = (sector == 0xffffffff) ? 0x00 : 0xf8;
cdb[10] = 0x02;
cdb[11] = 0x00;
#ifdef ENABLE_IOCTL_LOG
ioctl_log(ioctl->log, "Host CDB: %02X %02X %02X %02X %02X %02X "
"%02X %02X %02X %02X %02X %02X\n",
cdb[0], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5],
cdb[6], cdb[7], cdb[8], cdb[9], cdb[10], cdb[11]);
#endif
memset(sense, 0, sizeof(sense));
ret = sg_io_cmd(ioctl->fd, cdb, 12, buffer, len,
SG_DXFER_FROM_DEV, sense, &sense_len);
ioctl_log(ioctl->log, "ioctl_read_sector: ret = %d, sense_len = %d\n",
ret, sense_len);
if (sense_len >= 16) {
if ((sense[2] == 0x03) && (sense[12] == 0x11))
/* Treat this as an error to correctly indicate CIRC error to the guest. */
ret = 0;
ioctl_log(ioctl->log, "Host sense: %02X %02X %02X %02X %02X %02X %02X %02X\n",
sense[0], sense[1], sense[2], sense[3],
sense[4], sense[5], sense[6], sense[7]);
ioctl_log(ioctl->log, " %02X %02X %02X %02X %02X %02X %02X %02X\n",
sense[8], sense[9], sense[10], sense[11],
sense[12], sense[13], sense[14], sense[15]);
}
ret = ret ? 1 : -1;
data_len = len; /* sg_io_cmd handles this internally */
}
ioctl_log(ioctl->log, "ioctl_read_sector: final ret = %i\n", ret);
/* Construct raw subchannel data from Q only. */
if ((ret > 0) && !ioctl->is_dvd)
for (int i = 11; i >= 0; i--)
for (int j = 7; j >= 0; j--)
buffer[2352 + (i * 8) + j] = ((buffer[sc_offs + i] >> (7 - j)) & 0x01) << 6;
return ret;
}
static uint8_t
ioctl_get_track_type(const void *local, const uint32_t sector)
{
ioctl_t *ioctl = (ioctl_t *) local;
int track = ioctl_get_track(ioctl, sector);
raw_track_info_t *rti = (raw_track_info_t *) ioctl->cur_rti;
const raw_track_info_t *trk = &(rti[track]);
uint8_t ret = 0x00;
if (ioctl_is_track_audio(ioctl, sector))
ret = CD_TRACK_AUDIO;
else if (track != -1) for (int i = 0; i < ioctl->blocks_num; i++) {
const raw_track_info_t *ct = &(rti[i]);
const raw_track_info_t *nt = &(rti[i + 1]);
if (ct->point == 0xa0) {
uint8_t first = ct->pm;
uint8_t last = nt->pm;
if ((trk->point >= first) && (trk->point <= last)) {
ret = ct->ps;
break;
}
}
}
return ret;
}
static uint32_t
ioctl_get_last_block(const void *local)
{
const ioctl_t *ioctl = (const ioctl_t *) local;
raw_track_info_t *rti = (raw_track_info_t *) ioctl->cur_rti;
uint32_t lb = 0;
for (int i = (ioctl->blocks_num - 1); i >= 0; i--)
if (rti[i].point == 0xa2) {
lb = MSFtoLBA(rti[i].pm, rti[i].ps, rti[i].pf) - 151;
break;
}
ioctl_log(ioctl->log, "LBCapacity=%d\n", lb);
return lb;
}
static int
ioctl_read_dvd_structure(const void *local, const uint8_t layer, const uint8_t format,
uint8_t *buffer, uint32_t *info)
{
const ioctl_t *ioctl = (const ioctl_t *) local;
const int len = 2052;
uint8_t cdb[12];
uint8_t sense[64];
int sense_len = 0;
memset(cdb, 0, sizeof(cdb));
cdb[0] = 0xad; /* READ DVD STRUCTURE */
cdb[6] = layer; /* Layer Number */
cdb[7] = format; /* Format */
cdb[8] = 0x08; /* Allocation Length high */
cdb[9] = 0x04; /* Allocation Length low */
#ifdef ENABLE_IOCTL_LOG
ioctl_log(ioctl->log, "Host CDB: %02X %02X %02X %02X %02X %02X "
"%02X %02X %02X %02X %02X %02X\n",
cdb[0], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5],
cdb[6], cdb[7], cdb[8], cdb[9], cdb[10], cdb[11]);
#endif
memset(sense, 0, sizeof(sense));
int ret = sg_io_cmd(ioctl->fd, cdb, 12, buffer, len,
SG_DXFER_FROM_DEV, sense, &sense_len);
ioctl_log(ioctl->log, "ioctl_read_dvd_structure(): ret = %d, sense_len = %d\n",
ret, sense_len);
if (sense_len >= 16) {
/* Return sense to the host as is. */
ret = -((sense[2] << 16) | (sense[12] << 8) | sense[13]);
if (info != NULL)
*info = *(uint32_t *) &(sense[3]);
ioctl_log(ioctl->log, "Host sense: %02X %02X %02X %02X %02X %02X %02X %02X\n",
sense[0], sense[1], sense[2], sense[3],
sense[4], sense[5], sense[6], sense[7]);
ioctl_log(ioctl->log, " %02X %02X %02X %02X %02X %02X %02X %02X\n",
sense[8], sense[9], sense[10], sense[11],
sense[12], sense[13], sense[14], sense[15]);
} else
ret = ret ? 1 : 0;
return ret;
}
static int
ioctl_is_dvd(const void *local)
{
const ioctl_t *ioctl = (const ioctl_t *) local;
return ioctl->is_dvd;
}
static int
ioctl_has_audio(const void *local)
{
const ioctl_t *ioctl = (const ioctl_t *) local;
return ioctl->has_audio;
}
static int
ioctl_is_empty(const void *local)
{
const ioctl_t *ioctl = (const ioctl_t *) local;
uint8_t cdb[12];
uint8_t sense[64];
int sense_len = 0;
/* TEST UNIT READY */
memset(cdb, 0, sizeof(cdb));
cdb[0] = 0x00;
#ifdef ENABLE_IOCTL_LOG
ioctl_log(ioctl->log, "Host CDB: %02X %02X %02X %02X %02X %02X "
"%02X %02X %02X %02X %02X %02X\n",
cdb[0], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5],
cdb[6], cdb[7], cdb[8], cdb[9], cdb[10], cdb[11]);
#endif
memset(sense, 0, sizeof(sense));
int ret = sg_io_cmd(ioctl->fd, cdb, 6, NULL, 0,
SG_DXFER_NONE, sense, &sense_len);
ioctl_log(ioctl->log, "ioctl_is_empty(): ret = %d, sense_len = %d\n",
ret, sense_len);
if (sense_len >= 16) {
/* Check for NOT READY + MEDIUM NOT PRESENT. */
ret = ((sense[2] == SENSE_NOT_READY) && (sense[12] == ASC_MEDIUM_NOT_PRESENT));
ioctl_log(ioctl->log, "Host sense: %02X %02X %02X %02X %02X %02X %02X %02X\n",
sense[0], sense[1], sense[2], sense[3],
sense[4], sense[5], sense[6], sense[7]);
ioctl_log(ioctl->log, " %02X %02X %02X %02X %02X %02X %02X %02X\n",
sense[8], sense[9], sense[10], sense[11],
sense[12], sense[13], sense[14], sense[15]);
} else if (!ret)
ret = 1; /* SG_IO itself failed, assume empty */
else
ret = 0; /* No sense data and command succeeded = media present */
return ret;
}
/* Disc change polling thread. */
static void *
ioctl_poll_thread(void *arg)
{
ioctl_t *ioctl = (ioctl_t *) arg;
int was_empty = ioctl_is_empty(ioctl);
while (ioctl->poll_active) {
sleep(2); /* Poll every 2 seconds. */
if (!ioctl->poll_active)
break;
int now_empty = ioctl_is_empty(ioctl);
if (now_empty != was_empty) {
if (now_empty)
cdrom_set_empty(ioctl->dev);
else
cdrom_update_status(ioctl->dev);
was_empty = now_empty;
}
}
return NULL;
}
static void
ioctl_close(void *local)
{
ioctl_t *ioctl = (ioctl_t *) local;
/* Stop the polling thread. */
if (ioctl->poll_active) {
ioctl->poll_active = 0;
pthread_join(ioctl->poll_tid, NULL);
}
ioctl_close_handle(ioctl);
ioctl->fd = -1;
ioctl_log(ioctl->log, "Log closed\n");
log_close(ioctl->log);
ioctl->log = NULL;
free(ioctl);
}
static void
ioctl_load(const void *local)
{
ioctl_t *ioctl = (ioctl_t *) local;
if ((ioctl->fd >= 0) || ioctl_open_handle(ioctl)) {
/* Try to close the tray. */
(void) sys_ioctl(ioctl->fd, CDROMCLOSETRAY, NULL);
ioctl_read_raw_toc(ioctl);
}
}
static const cdrom_ops_t ioctl_ops = {
ioctl_get_track_info,
ioctl_get_raw_track_info,
ioctl_is_track_pre,
ioctl_read_sector,
ioctl_get_track_type,
ioctl_get_last_block,
ioctl_read_dvd_structure,
ioctl_is_dvd,
ioctl_has_audio,
ioctl_is_empty,
ioctl_close,
ioctl_load
};
/* Public functions. */
void *
ioctl_open(cdrom_t *dev, const char *drv)
{
ioctl_t *ioctl = (ioctl_t *) calloc(1, sizeof(ioctl_t));
if (ioctl != NULL) {
char n[1024] = { 0 };
sprintf(n, "CD-ROM %i IOCtl", dev->id + 1);
ioctl->log = log_open(n);
memset(ioctl->path, 0x00, sizeof(ioctl->path));
ioctl->fd = -1;
/* drv is "ioctl:///dev/sr0", extract the path part. */
snprintf(ioctl->path, sizeof(ioctl->path), "%s", &(drv[8]));
ioctl_log(ioctl->log, "Path is %s\n", ioctl->path);
ioctl->dev = dev;
dev->ops = &ioctl_ops;
ioctl_load(ioctl);
/* Start the disc change polling thread. */
ioctl->poll_active = 1;
if (pthread_create(&ioctl->poll_tid, NULL, ioctl_poll_thread, ioctl) != 0)
ioctl->poll_active = 0;
}
return ioctl;
}