]> git.unchartedbackwaters.co.uk Git - francis/winuae.git/commitdiff
od-unix: add storage and media backends
authorStefan Reinauer <stefan.reinauer@coreboot.org>
Thu, 4 Jun 2026 14:58:40 +0000 (07:58 -0700)
committerStefan Reinauer <stefan.reinauer@coreboot.org>
Thu, 11 Jun 2026 21:08:30 +0000 (14:08 -0700)
Add Unix CD, SCSI, SG_IO, CDDA, CHD, FLAC, and MP3 host-side helpers.

This provides the native device and media plumbing needed by the Unix
target without changing the shared storage controller emulation.

od-unix/FLAC/stream_decoder.h [new file with mode: 0644]
od-unix/blkdev_unix_ioctl.cpp [new file with mode: 0644]
od-unix/blkdev_unix_scsi_macos.cpp [new file with mode: 0644]
od-unix/blkdev_unix_sg.cpp [new file with mode: 0644]
od-unix/cda_play.cpp [new file with mode: 0644]
od-unix/cda_play.h [new file with mode: 0644]
od-unix/chd_unix_time.cpp [new file with mode: 0644]
od-unix/chd_unix_work.cpp [new file with mode: 0644]
od-unix/mp3decoder.h [new file with mode: 0644]

diff --git a/od-unix/FLAC/stream_decoder.h b/od-unix/FLAC/stream_decoder.h
new file mode 100644 (file)
index 0000000..b1bc0b9
--- /dev/null
@@ -0,0 +1,100 @@
+#ifndef WINUAE_OD_UNIX_FLAC_STREAM_DECODER_H
+#define WINUAE_OD_UNIX_FLAC_STREAM_DECODER_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+typedef int FLAC__bool;
+typedef uint8_t FLAC__byte;
+typedef int16_t FLAC__int16;
+typedef int32_t FLAC__int32;
+typedef uint64_t FLAC__uint64;
+
+typedef struct FLAC__StreamDecoder FLAC__StreamDecoder;
+struct FLAC__StreamDecoder {};
+
+enum {
+    FLAC__METADATA_TYPE_STREAMINFO = 0,
+    FLAC__METADATA_TYPE_CUESHEET = 5
+};
+
+typedef enum {
+    FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE = 0
+} FLAC__StreamDecoderWriteStatus;
+
+typedef enum {
+    FLAC__STREAM_DECODER_READ_STATUS_CONTINUE = 0,
+    FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM,
+    FLAC__STREAM_DECODER_READ_STATUS_ABORT
+} FLAC__StreamDecoderReadStatus;
+
+typedef enum {
+    FLAC__STREAM_DECODER_SEEK_STATUS_OK = 0
+} FLAC__StreamDecoderSeekStatus;
+
+typedef enum {
+    FLAC__STREAM_DECODER_TELL_STATUS_OK = 0
+} FLAC__StreamDecoderTellStatus;
+
+typedef enum {
+    FLAC__STREAM_DECODER_LENGTH_STATUS_OK = 0
+} FLAC__StreamDecoderLengthStatus;
+
+typedef int FLAC__StreamDecoderErrorStatus;
+
+typedef struct {
+    uint32_t blocksize;
+} FLAC__FrameHeader;
+
+typedef struct {
+    FLAC__FrameHeader header;
+} FLAC__Frame;
+
+typedef struct {
+    uint64_t total_samples;
+    uint32_t bits_per_sample;
+    uint32_t channels;
+} FLAC__StreamMetadata_StreamInfo;
+
+typedef struct {
+    int type;
+    union {
+        FLAC__StreamMetadata_StreamInfo stream_info;
+    } data;
+} FLAC__StreamMetadata;
+
+typedef FLAC__StreamDecoderReadStatus (*FLAC__StreamDecoderReadCallback)(const FLAC__StreamDecoder*, FLAC__byte[], size_t*, void*);
+typedef FLAC__StreamDecoderSeekStatus (*FLAC__StreamDecoderSeekCallback)(const FLAC__StreamDecoder*, FLAC__uint64, void*);
+typedef FLAC__StreamDecoderTellStatus (*FLAC__StreamDecoderTellCallback)(const FLAC__StreamDecoder*, FLAC__uint64*, void*);
+typedef FLAC__StreamDecoderLengthStatus (*FLAC__StreamDecoderLengthCallback)(const FLAC__StreamDecoder*, FLAC__uint64*, void*);
+typedef FLAC__bool (*FLAC__StreamDecoderEofCallback)(const FLAC__StreamDecoder*, void*);
+typedef FLAC__StreamDecoderWriteStatus (*FLAC__StreamDecoderWriteCallback)(const FLAC__StreamDecoder*, const FLAC__Frame*, const FLAC__int32* const[], void*);
+typedef void (*FLAC__StreamDecoderMetadataCallback)(const FLAC__StreamDecoder*, const FLAC__StreamMetadata*, void*);
+typedef void (*FLAC__StreamDecoderErrorCallback)(const FLAC__StreamDecoder*, FLAC__StreamDecoderErrorStatus, void*);
+
+static inline FLAC__StreamDecoder *FLAC__stream_decoder_new(void)
+{
+    return 0;
+}
+
+static inline void FLAC__stream_decoder_delete(FLAC__StreamDecoder*) {}
+static inline void FLAC__stream_decoder_set_md5_checking(FLAC__StreamDecoder*, FLAC__bool) {}
+static inline void FLAC__stream_decoder_set_metadata_respond(FLAC__StreamDecoder*, int) {}
+static inline int FLAC__stream_decoder_init_stream(
+    FLAC__StreamDecoder*,
+    FLAC__StreamDecoderReadCallback,
+    FLAC__StreamDecoderSeekCallback,
+    FLAC__StreamDecoderTellCallback,
+    FLAC__StreamDecoderLengthCallback,
+    FLAC__StreamDecoderEofCallback,
+    FLAC__StreamDecoderWriteCallback,
+    FLAC__StreamDecoderMetadataCallback,
+    FLAC__StreamDecoderErrorCallback,
+    void*)
+{
+    return 0;
+}
+static inline int FLAC__stream_decoder_process_until_end_of_metadata(FLAC__StreamDecoder*) { return 0; }
+static inline int FLAC__stream_decoder_process_until_end_of_stream(FLAC__StreamDecoder*) { return 0; }
+
+#endif /* WINUAE_OD_UNIX_FLAC_STREAM_DECODER_H */
diff --git a/od-unix/blkdev_unix_ioctl.cpp b/od-unix/blkdev_unix_ioctl.cpp
new file mode 100644 (file)
index 0000000..9a8ee51
--- /dev/null
@@ -0,0 +1,1670 @@
+/*
+* UAE - The Un*x Amiga Emulator
+*
+* CDROM/HD low level access code (IOCTL)
+*
+* Copyright 2024 Dimitris Panokostas
+* Copyright 2002-2010 Toni Wilen
+*
+*/
+
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#include "options.h"
+#include "threaddep/thread.h"
+#include "blkdev.h"
+#include "scsidev.h"
+#include "gui.h"
+#include "uae.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+
+#ifdef UAE_HOST_DARWIN
+// macOS — IOKit/DiskArbitration CD-ROM access (not available on iOS)
+#include <errno.h>
+#include <sys/disk.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <DiskArbitration/DiskArbitration.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/storage/IODVDMediaBSDClient.h>
+#include <IOKit/storage/IODVDMedia.h>
+#include <IOKit/storage/IODVDTypes.h>
+#include <IOKit/storage/IOCDMediaBSDClient.h>
+#include <IOKit/storage/IOCDMedia.h>
+#include <IOKit/storage/IOCDTypes.h>
+
+#ifndef ENOMEDIUM
+#define ENOMEDIUM ENXIO
+#endif
+
+#elif defined(__HAIKU__)
+#include <errno.h>
+#elif defined(__FreeBSD__)
+// FreeBSD
+#include <sys/cdio.h>
+#include <cam/scsi/scsi_all.h>
+#include <sys/errno.h>
+
+#else
+// Linux
+#include <linux/cdrom.h>
+#include <linux/hdreg.h>
+#include <linux/errno.h>
+#include <scsi/sg.h>
+
+#endif
+
+#include <cstring>
+#include <cstdio>
+#include <cstdlib>
+#include <cctype>
+#include <dirent.h>
+#include <string>
+#include <vector>
+
+#include "cda_play.h"
+#ifdef RETROPLATFORM
+#include "rp.h"
+#endif
+
+#define IOCTL_DATA_BUFFER 8192
+
+static std::vector<std::string> get_cd_drives()
+{
+       std::vector<std::string> results;
+#ifdef UAE_HOST_DARWIN
+       DASessionRef session = DASessionCreate(kCFAllocatorDefault);
+       if (!session) {
+               write_log("IOCTL: failed to create DiskArbitration session while scanning CD/DVD drives\n");
+               return results;
+       }
+
+       DIR *devdir = opendir("/dev");
+       if (!devdir) {
+               write_log("IOCTL: failed to open /dev while scanning CD/DVD drives\n");
+               CFRelease(session);
+               return results;
+       }
+
+       auto is_whole_disk_name = [](const char *name) -> bool {
+               if (!name || strncmp(name, "disk", 4) != 0)
+                       return false;
+               const char *p = name + 4;
+               if (!*p)
+                       return false;
+               while (*p) {
+                       if (!isdigit((unsigned char)*p))
+                               return false;
+                       p++;
+               }
+               return true;
+       };
+
+       auto cfstring_contains_ci = [](CFStringRef str, const char *needle) -> bool {
+               if (!str || !needle)
+                       return false;
+               char buf[256];
+               if (!CFStringGetCString(str, buf, sizeof(buf), kCFStringEncodingUTF8))
+                       return false;
+               std::string hay(buf);
+               std::string ndl(needle);
+               for (char &c : hay)
+                       c = (char)tolower((unsigned char)c);
+               for (char &c : ndl)
+                       c = (char)tolower((unsigned char)c);
+               return hay.find(ndl) != std::string::npos;
+       };
+
+       struct dirent *entry;
+       while ((entry = readdir(devdir)) != NULL) {
+               if (!is_whole_disk_name(entry->d_name))
+                       continue;
+
+               DADiskRef disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, entry->d_name);
+               if (!disk)
+                       continue;
+
+               CFDictionaryRef desc = DADiskCopyDescription(disk);
+               if (!desc) {
+                       CFRelease(disk);
+                       continue;
+               }
+
+               CFBooleanRef whole = (CFBooleanRef)CFDictionaryGetValue(desc, kDADiskDescriptionMediaWholeKey);
+               CFBooleanRef ejectable = (CFBooleanRef)CFDictionaryGetValue(desc, kDADiskDescriptionMediaEjectableKey);
+               CFBooleanRef removable = (CFBooleanRef)CFDictionaryGetValue(desc, kDADiskDescriptionMediaRemovableKey);
+               CFStringRef kind = (CFStringRef)CFDictionaryGetValue(desc, kDADiskDescriptionMediaKindKey);
+               if (whole == kCFBooleanTrue && (ejectable == kCFBooleanTrue || removable == kCFBooleanTrue) &&
+                       (cfstring_contains_ci(kind, "cd") || cfstring_contains_ci(kind, "dvd"))) {
+                       std::string path("/dev/");
+                       path.append(entry->d_name);
+                       results.emplace_back(path);
+               }
+
+               CFRelease(desc);
+               CFRelease(disk);
+       }
+
+       closedir(devdir);
+       CFRelease(session);
+#elif defined(__linux__)
+       DIR *sysblock = opendir("/sys/block");
+       if (!sysblock) {
+               write_log("IOCTL: failed to open /sys/block while scanning CD/DVD drives\n");
+               return results;
+       }
+
+       struct dirent *entry;
+       while ((entry = readdir(sysblock)) != NULL) {
+               if (entry->d_name[0] == '.')
+                       continue;
+               char typepath[MAX_DPATH];
+               snprintf(typepath, sizeof(typepath), "/sys/block/%s/device/type", entry->d_name);
+               FILE *f = fopen(typepath, "r");
+               if (!f)
+                       continue;
+               int type = -1;
+               int parsed = fscanf(f, "%d", &type);
+               fclose(f);
+               if (parsed == 1 && type == INQ_ROMD) {
+                       std::string path("/dev/");
+                       path.append(entry->d_name);
+                       results.emplace_back(path);
+               }
+       }
+
+       closedir(sysblock);
+#endif
+       return results;
+}
+
+#ifdef WITH_SCSI_IOCTL
+
+
+
+struct dev_info_ioctl {
+       int fd;
+       uae_u8* tempbuffer;
+       TCHAR drvletter[30];
+       TCHAR drvlettername[30];
+       TCHAR devname[30];
+       int type;
+#ifdef __linux__
+       struct cdrom_tocentry cdromtoc;
+#endif
+       uae_u8 trackmode[100];
+       UINT errormode;
+       int fullaccess;
+       struct device_info di;
+       bool open;
+       bool usesptiread;
+       bool changed;
+       struct cda_play cda;
+};
+
+static struct dev_info_ioctl ciw32[MAX_TOTAL_SCSI_DEVICES];
+static int unittable[MAX_TOTAL_SCSI_DEVICES];
+static int bus_open;
+static uae_sem_t play_sem;
+
+static int sys_cddev_open(struct dev_info_ioctl* ciw, int unitnum);
+static void sys_cddev_close(struct dev_info_ioctl* ciw, int unitnum);
+
+static int getunitnum(struct dev_info_ioctl* ciw)
+{
+       if (!ciw)
+               return -1;
+       int idx = (int)(ciw - &ciw32[0]);
+       for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
+               if (unittable[i] - 1 == idx)
+                       return i;
+       }
+       return -1;
+}
+
+static struct dev_info_ioctl* unitcheck(int unitnum)
+{
+       if (unitnum < 0 || unitnum >= MAX_TOTAL_SCSI_DEVICES)
+               return NULL;
+       if (unittable[unitnum] <= 0)
+               return NULL;
+       unitnum = unittable[unitnum] - 1;
+       if (ciw32[unitnum].drvletter[0] == 0)
+               return NULL;
+       return &ciw32[unitnum];
+}
+
+static struct dev_info_ioctl* unitisopen(int unitnum)
+{
+       struct dev_info_ioctl* di = unitcheck(unitnum);
+       if (!di)
+               return NULL;
+       if (di->open == false)
+               return NULL;
+       return di;
+}
+
+static int unix_ioctl_error(struct dev_info_ioctl* ciw, int unitnum, const TCHAR* format, ...)
+{
+       va_list arglist;
+       TCHAR buf[1000];
+       int err = errno;
+#ifdef __linux__
+       if (err == ENOMEDIUM) {
+               write_log(_T("IOCTL: media change, re-opening device\n"));
+               sys_cddev_close(ciw, unitnum);
+               if (sys_cddev_open(ciw, unitnum))
+                       write_log(_T("IOCTL: re-opening failed!\n"));
+               return -1;
+       }
+#endif
+       va_start(arglist, format);
+       _vsntprintf(buf, sizeof buf / sizeof(TCHAR), format, arglist);
+       write_log(_T("IOCTL ERR: unit=%d,%s,%d: %s\n"), unitnum, buf, err, strerror(err));
+       va_end(arglist);
+       return err;
+}
+
+static int close_createfile(struct dev_info_ioctl* ciw)
+{
+       ciw->fullaccess = 0;
+       if (ciw->fd != -1) {
+               if (log_scsi)
+                       write_log(_T("IOCTL: IOCTL close\n"));
+               close(ciw->fd);
+               if (log_scsi)
+                       write_log(_T("IOCTL: IOCTL close completed\n"));
+               ciw->fd = -1;
+               return 1;
+       }
+       return 0;
+}
+
+static int open_createfile(struct dev_info_ioctl* ciw, int fullaccess)
+{
+       if (ciw->fd != -1) {
+               //if (fullaccess && ciw->fullaccess == 0) {
+                       close_createfile(ciw);
+               //}
+               // else {
+               //      return 1;
+               // }
+       }
+       if (log_scsi)
+               write_log(_T("IOCTL: opening IOCTL %s\n"), ciw->devname);
+       ciw->fd = open(ciw->devname, fullaccess ? O_RDWR : O_RDONLY);
+#ifdef UAE_HOST_DARWIN
+       if (ciw->fd == -1)
+               ciw->fd = open(ciw->devname, (fullaccess ? O_RDWR : O_RDONLY) | O_NONBLOCK);
+       if (ciw->fd == -1) {
+               if (!strncmp(ciw->devname, "/dev/disk", 9)) {
+                       char alt[64];
+                       snprintf(alt, sizeof(alt), "/dev/rdisk%s", ciw->devname + 9);
+                       ciw->fd = open(alt, (fullaccess ? O_RDWR : O_RDONLY) | O_NONBLOCK);
+                       if (ciw->fd == -1)
+                               ciw->fd = open(alt, fullaccess ? O_RDWR : O_RDONLY);
+                       if (ciw->fd != -1) {
+                               strncpy(ciw->devname, alt, sizeof(ciw->devname));
+                               ciw->devname[sizeof(ciw->devname) - 1] = 0;
+                       }
+               } else if (!strncmp(ciw->devname, "/dev/rdisk", 10)) {
+                       // It's already the rdisk, maybe try the block device for kicks
+                       char alt[64];
+                       snprintf(alt, sizeof(alt), "/dev/disk%s", ciw->devname + 10);
+                       ciw->fd = open(alt, (fullaccess ? O_RDWR : O_RDONLY) | O_NONBLOCK);
+                       if (ciw->fd == -1)
+                               ciw->fd = open(alt, fullaccess ? O_RDWR : O_RDONLY);
+                       if (ciw->fd != -1) {
+                               strncpy(ciw->devname, alt, sizeof(ciw->devname));
+                               ciw->devname[sizeof(ciw->devname) - 1] = 0;
+                       }
+               }
+       }
+#endif
+       if (ciw->fd == -1) {
+               int err = errno;
+               write_log(_T("IOCTL: failed to open '%s', err=%d\n"), ciw->devname, err);
+               return 0;
+       }
+       ciw->fullaccess = fullaccess;
+       if (log_scsi)
+               write_log(_T("IOCTL: IOCTL open completed\n"));
+       return 1;
+}
+
+
+#ifdef __linux__
+static int do_raw_scsi(struct dev_info_ioctl* ciw, int unitnum, unsigned char* cmd, int cmdlen, unsigned char* data, int datalen) {
+       struct sg_io_hdr io_hdr;
+       unsigned char sense_buffer[32];
+       memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+       io_hdr.interface_id = 'S';
+       io_hdr.cmd_len = cmdlen;
+       io_hdr.mx_sb_len = sizeof(sense_buffer);
+       io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+       io_hdr.dxfer_len = datalen;
+       io_hdr.dxferp = data;
+       io_hdr.cmdp = cmd;
+       io_hdr.sbp = sense_buffer;
+       io_hdr.timeout = 20000; // 20 seconds
+
+       if (ioctl(ciw->fd, SG_IO, &io_hdr) < 0) {
+               perror("SG_IO");
+               return 0;
+       }
+       return io_hdr.dxfer_len;
+}
+#endif
+
+static void sub_deinterleave(const uae_u8* s, uae_u8* d)
+{
+       for (int i = 0; i < 8 * 12; i++) {
+               int dmask = 0x80;
+               int smask = 1 << (7 - (i / 12));
+               (*d) = 0;
+               for (int j = 0; j < 8; j++) {
+                       (*d) |= (s[(i % 12) * 8 + j] & smask) ? dmask : 0;
+                       dmask >>= 1;
+               }
+               d++;
+       }
+}
+
+#ifdef __linux__
+static int spti_read(struct dev_info_ioctl* ciw, int unitnum, uae_u8* data, int sector, int sectorsize)
+{
+       uae_u8 cmd[12] = { 0xbe, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 };
+       int tlen = sectorsize;
+
+       //write_log(_T("spti_read %d %d %d\n"), unitnum, sector, sectorsize);
+
+       if (sectorsize == 2048 || sectorsize == 2336 || sectorsize == 2328) {
+               cmd[9] |= 1 << 4; // userdata
+       }
+       else if (sectorsize >= 2352) {
+               cmd[9] |= 1 << 4; // userdata
+               cmd[9] |= 1 << 3; // EDC&ECC
+               cmd[9] |= 1 << 7; // sync
+               cmd[9] |= 3 << 5; // header code
+               if (sectorsize > 2352) {
+                       cmd[10] |= 1; // RAW P-W
+               }
+               if (sectorsize > 2352 + SUB_CHANNEL_SIZE) {
+                       cmd[9] |= 0x2 << 1; // C2
+               }
+       }
+       cmd[3] = (uae_u8)(sector >> 16);
+       cmd[4] = (uae_u8)(sector >> 8);
+       cmd[5] = (uae_u8)(sector >> 0);
+       if (unitnum >= 0)
+               gui_flicker_led(LED_CD, unitnum, LED_CD_ACTIVE);
+       int len = sizeof cmd;
+       return do_raw_scsi(ciw, unitnum, cmd, len, data, tlen);
+}
+#else
+static int spti_read(struct dev_info_ioctl* ciw, int unitnum, uae_u8* data, int sector, int sectorsize)
+{
+       (void)ciw;
+       (void)unitnum;
+       (void)data;
+       (void)sector;
+       (void)sectorsize;
+       return 0;
+}
+#endif
+
+extern void encode_l2(uae_u8* p, int address);
+
+#ifdef UAE_HOST_DARWIN
+static int mac_cd_read_sector(struct dev_info_ioctl* ciw, int sector, uae_u8* dst, int num_sectors, uae_u8 sectorType)
+{
+       dk_cd_read_t rd{};
+       uint64_t sectorsize = (sectorType == kCDSectorTypeCDDA) ? kCDSectorSizeCDDA : kCDSectorSizeMode1;
+       rd.offset = (uint64_t)sector * sectorsize;
+       rd.sectorArea = kCDSectorAreaUser;
+       rd.sectorType = sectorType;
+       rd.bufferLength = (uint32_t)(num_sectors * sectorsize);
+       rd.buffer = dst;
+       if (ioctl(ciw->fd, DKIOCCDREAD, &rd) == -1)
+               return 0;
+       return rd.bufferLength;
+}
+
+static int mac_cd_read_mode1(struct dev_info_ioctl* ciw, int sector, uae_u8* dst)
+{
+       if (mac_cd_read_sector(ciw, sector, dst, 1, kCDSectorTypeMode1))
+               return kCDSectorSizeMode1;
+       if (mac_cd_read_sector(ciw, sector, dst, 1, kCDSectorTypeMode2Form1))
+               return kCDSectorSizeMode1;
+       return 0;
+}
+#endif
+
+static int read2048(struct dev_info_ioctl* ciw, int sector)
+{
+#ifdef UAE_HOST_DARWIN
+       return mac_cd_read_mode1(ciw, sector, ciw->tempbuffer);
+#else
+       off_t offset = (off_t)sector * 2048;
+
+       if (lseek(ciw->fd, offset, SEEK_SET) == (off_t)-1) {
+               return 0;
+       }
+       ssize_t dtotal = read(ciw->fd, ciw->tempbuffer, 2048);
+       return dtotal == 2048 ? 2048 : 0;
+#endif
+}
+
+static int read_block(struct dev_info_ioctl* ciw, int unitnum, uae_u8* data, int sector, int size, int sectorsize)
+{
+       uae_u8* p = ciw->tempbuffer;
+       int ret;
+       int origsize = size;
+       int origsector = sector;
+       uae_u8* origdata = data;
+       bool got;
+
+retry:
+       if (!open_createfile(ciw, ciw->usesptiread ? 1 : 0))
+               return 0;
+       ret = 0;
+       while (size > 0) {
+               int track = cdtracknumber(&ciw->di.toc, sector);
+               if (track < 0)
+                       return 0;
+               got = false;
+#ifdef UAE_HOST_DARWIN
+               if (isaudiotrack(&ciw->di.toc, sector) && sectorsize >= 2352 && data) {
+                       uae_u8* temp_cd_buf = (uae_u8*)malloc(size * 2352);
+                       if (temp_cd_buf) {
+                               if (!mac_cd_read_sector(ciw, sector, temp_cd_buf, size, kCDSectorTypeCDDA)) {
+                                       free(temp_cd_buf);
+                                       return ret;
+                               }
+                               for (int i = 0; i < size; i++) {
+                                       memcpy(data + (i * sectorsize), temp_cd_buf + (i * 2352), 2352);
+                               }
+                               free(temp_cd_buf);
+                               ciw->cda.cd_last_pos = sector + size - 1;
+                               data += sectorsize * size;
+                               ret += sectorsize * size;
+                               sector += size;
+                               size = 0;
+                               continue;
+                       }
+               }
+#endif
+               got = false;
+               if (!ciw->usesptiread && sectorsize == 2048 && ciw->trackmode[track] == 0) {
+                       if (read2048(ciw, sector) == 2048) {
+                               memcpy(data, p, 2048);
+                               data += sectorsize;
+                               ret += sectorsize;
+                               got = true;
+                       }
+               }
+               if (!got && !ciw->usesptiread) {
+                       int len = spti_read(ciw, unitnum, data, sector, sectorsize);
+                       if (len) {
+                               if (data) {
+                                       memcpy(data, p, sectorsize);
+                                       data += sectorsize;
+                                       ret += sectorsize;
+                               }
+                               got = true;
+                       }
+               }
+               if (!got) {
+                       int dtotal = read2048(ciw, sector);
+                       if (dtotal != 2048)
+                               return ret;
+                       if (sectorsize >= 2352) {
+                               memset(data, 0, 16);
+                               memcpy(data + 16, p, 2048);
+                               encode_l2(data, sector + 150);
+                               if (sectorsize > 2352)
+                                       memset(data + 2352, 0, sectorsize - 2352);
+                               data += sectorsize;
+                               ret += sectorsize;
+                       }
+                       else if (sectorsize == 2048) {
+                               memcpy(data, p, 2048);
+                               data += sectorsize;
+                               ret += sectorsize;
+                       }
+                       got = true;
+               }
+               sector++;
+               size--;
+       }
+       return ret;
+}
+
+static int read_block_cda(struct cda_play* cda, int unitnum, uae_u8* data, int sector, int size, int sectorsize)
+{
+       return read_block(&ciw32[unitnum], unitnum, data, sector, size, sectorsize);
+}
+
+/* pause/unpause CD audio */
+static int ioctl_command_pause(int unitnum, int paused)
+{
+       struct dev_info_ioctl* ciw = unitisopen(unitnum);
+       if (!ciw)
+               return -1;
+       int old = ciw->cda.cdda_paused;
+       if ((paused && ciw->cda.cdda_play) || !paused)
+               ciw->cda.cdda_paused = paused;
+       return old;
+}
+
+
+/* stop CD audio */
+static int ioctl_command_stop(int unitnum)
+{
+       struct dev_info_ioctl* ciw = unitisopen(unitnum);
+       if (!ciw)
+               return 0;
+
+       ciw_cdda_stop(&ciw->cda);
+
+       return 1;
+}
+
+static uae_u32 ioctl_command_volume(int unitnum, uae_u16 volume_left, uae_u16 volume_right)
+{
+       struct dev_info_ioctl* ciw = unitisopen(unitnum);
+       if (!ciw)
+               return -1;
+       uae_u32 old = (ciw->cda.cdda_volume[1] << 16) | (ciw->cda.cdda_volume[0] << 0);
+       ciw->cda.cdda_volume[0] = volume_left;
+       ciw->cda.cdda_volume[1] = volume_right;
+       return old;
+}
+
+/* play CD audio */
+static int ioctl_command_play(int unitnum, int startlsn, int endlsn, int scan, play_status_callback statusfunc, play_subchannel_callback subfunc)
+{
+       struct dev_info_ioctl* ciw = unitisopen(unitnum);
+       if (!ciw)
+               return 0;
+
+       ciw->cda.di = &ciw->di;
+       ciw->cda.cdda_play_finished = 0;
+       ciw->cda.cdda_subfunc = subfunc;
+       ciw->cda.cdda_statusfunc = statusfunc;
+       ciw->cda.cdda_scan = scan > 0 ? 10 : (scan < 0 ? 10 : 0);
+       ciw->cda.cdda_delay = ciw_cdda_setstate(&ciw->cda, -1, -1);
+       ciw->cda.cdda_delay_frames = ciw_cdda_setstate(&ciw->cda, -2, -1);
+       ciw_cdda_setstate(&ciw->cda, AUDIO_STATUS_NOT_SUPPORTED, -1);
+       ciw->cda.read_block = read_block_cda;
+
+       if (!open_createfile(ciw, 0)) {
+               ciw_cdda_setstate(&ciw->cda, AUDIO_STATUS_PLAY_ERROR, -1);
+               return 0;
+       }
+       if (!isaudiotrack(&ciw->di.toc, startlsn)) {
+               ciw_cdda_setstate(&ciw->cda, AUDIO_STATUS_PLAY_ERROR, -1);
+               return 0;
+       }
+       if (!ciw->cda.cdda_play) {
+               uae_start_thread(_T("ioctl_cdda_play"), ciw_cdda_play, &ciw->cda, NULL);
+       }
+       ciw->cda.cdda_start = startlsn;
+       ciw->cda.cdda_end = endlsn;
+       ciw->cda.cd_last_pos = ciw->cda.cdda_start;
+       ciw->cda.cdda_play++;
+
+       return 1;
+}
+
+/* read qcode */
+static int ioctl_command_qcode(int unitnum, uae_u8* buf, int sector, bool all)
+{
+#ifdef UAE_HOST_DARWIN
+       struct dev_info_ioctl* ciw = unitisopen(unitnum);
+       if (!ciw)
+               return 0;
+
+       if (all)
+               return 0;
+
+       memset(buf, 0, SUBQ_SIZE);
+       uae_u8* p = buf;
+
+       int status = AUDIO_STATUS_NO_STATUS;
+       if (ciw->cda.cdda_play) {
+               status = AUDIO_STATUS_IN_PROGRESS;
+               if (ciw->cda.cdda_paused)
+                       status = AUDIO_STATUS_PAUSED;
+       } else if (ciw->cda.cdda_play_finished) {
+               status = AUDIO_STATUS_PLAY_COMPLETE;
+       }
+
+       p[1] = status;
+       p[3] = 12;
+       p = buf + 4;
+
+       int pos = (sector < 0) ? ciw->cda.cd_last_pos : sector;
+
+       int trk = cdtracknumber(&ciw->di.toc, pos);
+       if (trk < 0)
+               return 0;
+
+       int start = 0;
+       int end = INT_MAX;
+       for (int i = ciw->di.toc.first_track; i <= ciw->di.toc.last_track; ++i) {
+               struct cd_toc* t = &ciw->di.toc.toc[i];
+               if (t->point != i)
+                       continue;
+               start = t->paddress;
+               if (i < ciw->di.toc.last_track) {
+                       struct cd_toc* tn = &ciw->di.toc.toc[i + 1];
+                       end = tn->paddress;
+               }
+               if (pos >= start && pos < end) {
+                       p[0] = (t->control << 4) | (t->adr << 0);
+                       p[1] = tobcd(i);
+                       p[2] = tobcd(1);
+                       int msf = lsn2msf(pos);
+                       tolongbcd(p + 7, msf);
+                       msf = lsn2msf(pos - start - 150);
+                       tolongbcd(p + 3, msf);
+                       return 1;
+               }
+       }
+       return 0;
+#elif defined(__linux__)
+       struct dev_info_ioctl* ciw = unitisopen(unitnum);
+       if (!ciw)
+               return 0;
+
+       uae_u8* p;
+       int trk;
+       cdrom_tocentry* toc = &ciw->cdromtoc;
+       int pos;
+       int msf;
+       int start, end;
+       int status;
+       bool valid = false;
+       bool regenerate = true;
+
+       if (all)
+               return 0;
+
+       memset(buf, 0, SUBQ_SIZE);
+       p = buf;
+
+       status = AUDIO_STATUS_NO_STATUS;
+       if (ciw->cda.cdda_play) {
+               status = AUDIO_STATUS_IN_PROGRESS;
+               if (ciw->cda.cdda_paused)
+                       status = AUDIO_STATUS_PAUSED;
+       }
+       else if (ciw->cda.cdda_play_finished) {
+               status = AUDIO_STATUS_PLAY_COMPLETE;
+       }
+
+       p[1] = status;
+       p[3] = 12;
+
+       p = buf + 4;
+
+       if (sector < 0)
+               pos = ciw->cda.cd_last_pos;
+       else
+               pos = sector;
+
+       if (!regenerate) {
+               if (sector < 0 && ciw->cda.subcodevalid && ciw->cda.cdda_play) {
+                       uae_sem_wait(&ciw->cda.sub_sem2);
+                       uae_u8 subbuf[SUB_CHANNEL_SIZE];
+                       sub_deinterleave(ciw->cda.subcodebuf, subbuf);
+                       memcpy(p, subbuf + 12, 12);
+                       uae_sem_post(&ciw->cda.sub_sem2);
+                       valid = true;
+               }
+               if (!valid && sector >= 0) {
+                       unsigned int len;
+                       uae_sem_wait(&ciw->cda.sub_sem);
+                       struct cdrom_subchnl subchnl;
+                       subchnl.cdsc_format = CDROM_MSF;
+                       if (ioctl(ciw->fd, CDROMSUBCHNL, &subchnl) == -1) {
+                               perror("CDROMSUBCHNL ioctl failed");
+                       }
+                       uae_u8 subbuf[SUB_CHANNEL_SIZE];
+                       sub_deinterleave((uae_u8*)&subchnl, subbuf);
+                       uae_sem_post(&ciw->cda.sub_sem);
+                       memcpy(p, subbuf + 12, 12);
+                       valid = true;
+               }
+       }
+
+       if (!valid) {
+               start = end = 0;
+               struct cdrom_tocentry tocentry;
+               for (trk = 0; trk <= ciw->di.toc.last_track; trk++) {
+                       tocentry.cdte_track = trk;
+                       tocentry.cdte_format = CDROM_MSF;
+                       if (ioctl(ciw->fd, CDROMREADTOCENTRY, &tocentry) == -1) {
+                               perror("CDROMREADTOCENTRY ioctl failed");
+                               return 0;
+                       }
+                       start = msf2lsn((tocentry.cdte_addr.msf.minute << 16) | (tocentry.cdte_addr.msf.second << 8) | tocentry.cdte_addr.msf.frame);
+                       if (trk < ciw->di.toc.last_track) {
+                               tocentry.cdte_track = trk + 1;
+                               if (ioctl(ciw->fd, CDROMREADTOCENTRY, &tocentry) == -1) {
+                                       perror("CDROMREADTOCENTRY ioctl failed");
+                                       return 0;
+                               }
+                               end = msf2lsn((tocentry.cdte_addr.msf.minute << 16) | (tocentry.cdte_addr.msf.second << 8) | tocentry.cdte_addr.msf.frame);
+                       }
+                       else {
+                               end = INT_MAX;
+                       }
+                       if (pos < start)
+                               break;
+                       if (pos >= start && pos < end)
+                               break;
+               }
+               p[0] = (tocentry.cdte_ctrl << 4) | (tocentry.cdte_adr << 0);
+               p[1] = tobcd(trk + 1);
+               p[2] = tobcd(1);
+               msf = lsn2msf(pos);
+               tolongbcd(p + 7, msf);
+               msf = lsn2msf(pos - start - 150);
+               tolongbcd(p + 3, msf);
+       }
+
+       return 1;
+#else
+  //FreeBSD not implemented
+  return 0;
+#endif
+}
+
+static int ioctl_command_rawread(int unitnum, uae_u8* data, int sector, int size, int sectorsize, uae_u32 extra)
+{
+       struct dev_info_ioctl* ciw = unitisopen(unitnum);
+       if (!ciw)
+               return 0;
+
+       uae_u8* p = ciw->tempbuffer;
+       int ret = 0;
+
+       if (log_scsi)
+               write_log(_T("IOCTL rawread unit=%d sector=%d blocksize=%d\n"), unitnum, sector, sectorsize);
+       ciw_cdda_stop(&ciw->cda);
+       gui_flicker_led(LED_CD, unitnum, LED_CD_ACTIVE);
+       if (sectorsize > 0) {
+               if (sectorsize != 2336 && sectorsize != 2352 && sectorsize != 2048 &&
+                       sectorsize != 2336 + 96 && sectorsize != 2352 + 96 && sectorsize != 2048 + 96)
+                       return 0;
+               while (size-- > 0) {
+                       if (!read_block(ciw, unitnum, data, sector, 1, sectorsize))
+                               break;
+                       ciw->cda.cd_last_pos = sector;
+                       data += sectorsize;
+                       ret += sectorsize;
+                       sector++;
+               }
+       }
+       else {
+               uae_u8 sectortype = extra >> 16;
+               uae_u8 cmd9 = extra >> 8;
+               int sync = (cmd9 >> 7) & 1;
+               int headercodes = (cmd9 >> 5) & 3;
+               int userdata = (cmd9 >> 4) & 1;
+               int edcecc = (cmd9 >> 3) & 1;
+               int errorfield = (cmd9 >> 1) & 3;
+               uae_u8 subs = extra & 7;
+               if (subs != 0 && subs != 1 && subs != 2 && subs != 4)
+                       return -1;
+               if (errorfield >= 3)
+                       return -1;
+               uae_u8* d = data;
+
+               if (isaudiotrack(&ciw->di.toc, sector)) {
+
+                       if (sectortype != 0 && sectortype != 1)
+                               return -2;
+
+                       for (int i = 0; i < size; i++) {
+                               uae_u8* odata = data;
+                               int blocksize = errorfield == 0 ? 2352 : (errorfield == 1 ? 2352 + 294 : 2352 + 296);
+                               int readblocksize = errorfield == 0 ? 2352 : 2352 + 296;
+
+                               if (!read_block(ciw, unitnum, NULL, sector, 1, readblocksize)) {
+                                       return ret;
+                               }
+                               ciw->cda.cd_last_pos = sector;
+
+                               if (subs == 0) {
+                                       memcpy(data, p, blocksize);
+                                       data += blocksize;
+                               }
+                               else if (subs == 4) { // all, de-interleaved
+                                       memcpy(data, p, blocksize);
+                                       data += blocksize;
+                                       sub_to_deinterleaved(p + readblocksize, data);
+                                       data += SUB_CHANNEL_SIZE;
+                               }
+                               else if (subs == 2) { // q-only
+                                       memcpy(data, p, blocksize);
+                                       data += blocksize;
+                                       uae_u8 subdata[SUB_CHANNEL_SIZE];
+                                       sub_to_deinterleaved(p + readblocksize, subdata);
+                                       memcpy(data, subdata + SUB_ENTRY_SIZE, SUB_ENTRY_SIZE);
+                                       p += SUB_ENTRY_SIZE;
+                               }
+                               else if (subs == 1) { // all, interleaved
+                                       memcpy(data, p, blocksize);
+                                       memcpy(data + blocksize, p + readblocksize, SUB_CHANNEL_SIZE);
+                                       data += blocksize + SUB_CHANNEL_SIZE;
+                               }
+                               ret += (int)(data - odata);
+                               sector++;
+                       }
+               }
+
+
+       }
+       return ret;
+}
+
+static int ioctl_command_readwrite(int unitnum, int sector, int size, int do_write, uae_u8* data)
+{
+       struct dev_info_ioctl* ciw = unitisopen(unitnum);
+       if (!ciw)
+               return 0;
+
+#ifdef UAE_HOST_DARWIN
+       // macOS: prefer raw CD reads via DKIOCCDREAD to avoid block-size mismatches
+       if (!do_write && data) {
+               int remaining = size;
+               int current = sector;
+               int ok = 1;
+               while (remaining-- > 0) {
+                       if (!mac_cd_read_mode1(ciw, current, data)) {
+                               ok = 0;
+                               break;
+                       }
+                       data += kCDSectorSizeMode1;
+                       current++;
+               }
+               return ok;
+       }
+#endif
+
+       if (ciw->usesptiread)
+               return ioctl_command_rawread(unitnum, data, sector, size, 2048, 0);
+
+       ciw_cdda_stop(&ciw->cda);
+
+       ssize_t dtotal;
+       int cnt = 3;
+       uae_u8* p = ciw->tempbuffer;
+       int blocksize = ciw->di.bytespersector > 0 ? ciw->di.bytespersector : 2048;
+
+       if (!open_createfile(ciw, 0))
+               return 0;
+       ciw->cda.cd_last_pos = sector;
+       while (cnt-- > 0) {
+               off_t offset = (off_t)sector * ciw->di.bytespersector;
+               gui_flicker_led(LED_CD, unitnum, LED_CD_ACTIVE);
+               if (lseek(ciw->fd, offset, SEEK_SET) == (off_t)-1) {
+                       if (unix_ioctl_error(ciw, unitnum, _T("SetFilePointer")) < 0)
+                               continue;
+                       return 0;
+               }
+               break;
+       }
+       while (size-- > 0) {
+               gui_flicker_led(LED_CD, unitnum, LED_CD_ACTIVE);
+               if (do_write) {
+                       if (data) {
+                               memcpy(p, data, blocksize);
+                               data += blocksize;
+                       }
+                       if (write(ciw->fd, p, blocksize) != blocksize) {
+                               int err;
+                               err = unix_ioctl_error(ciw, unitnum, _T("write"));
+                               if (err < 0)
+                                       continue;
+                               if (err == EROFS)
+                                       return -1;
+                               return 0;
+                       }
+               }
+               else {
+                       dtotal = read(ciw->fd, p, blocksize);
+                       if (dtotal != blocksize) {
+                               if (unix_ioctl_error(ciw, unitnum, _T("read")) < 0)
+                                       continue;
+                               return 0;
+                       }
+                       if (dtotal == 0) {
+                               static int reported;
+                               /* ESS Mega (CDTV) "fake" data area returns zero bytes and no error.. */
+                               spti_read(ciw, unitnum, data, sector, 2048);
+                               if (reported++ < 100)
+                                       write_log(_T("IOCTL unit %d, sector %d: read()==0. SPTI=%d\n"), unitnum, sector, errno);
+                               return 1;
+                       }
+                       if (data) {
+                               memcpy(data, p, blocksize);
+                               data += blocksize;
+                       }
+               }
+               gui_flicker_led(LED_CD, unitnum, LED_CD_ACTIVE);
+       }
+       return 1;
+
+}
+
+static int ioctl_command_write(int unitnum, uae_u8* data, int sector, int size)
+{
+       return ioctl_command_readwrite(unitnum, sector, size, 1, data);
+}
+
+static int ioctl_command_read(int unitnum, uae_u8* data, int sector, int size)
+{
+       return ioctl_command_readwrite(unitnum, sector, size, 0, data);
+}
+
+static int fetch_geometry(struct dev_info_ioctl* ciw, int unitnum, struct device_info* di)
+{
+#ifdef UAE_HOST_DARWIN
+       if (!open_createfile(ciw, 0))
+               return 0;
+
+       uae_sem_wait(&ciw->cda.sub_sem);
+
+       uint32_t blocksize = 0;
+       if (ioctl(ciw->fd, DKIOCGETBLOCKSIZE, &blocksize) == 0 && blocksize > 0)
+               ciw->di.bytespersector = (int)blocksize;
+       else
+               ciw->di.bytespersector = 2048;
+
+       dk_cd_read_toc_t rtoc{};
+       unsigned char buf[4096];
+       memset(buf, 0, sizeof(buf));
+       rtoc.format = kCDTOCFormatTOC;
+       rtoc.formatAsTime = 1;
+       rtoc.address.track = 0;
+       rtoc.bufferLength = sizeof(buf);
+       rtoc.buffer = buf;
+       int ok = ioctl(ciw->fd, DKIOCCDREADTOC, &rtoc);
+       if (ok == -1 && !strncmp(ciw->devname, "/dev/rdisk", 10)) {
+               char alt[64];
+               snprintf(alt, sizeof(alt), "/dev/disk%s", ciw->devname + 10);
+               int fd2 = open(alt, O_RDONLY);
+               if (fd2 == -1)
+                       fd2 = open(alt, O_RDONLY | O_NONBLOCK);
+               if (fd2 != -1) {
+                       ok = ioctl(fd2, DKIOCCDREADTOC, &rtoc);
+                       close(fd2);
+               }
+       }
+       if (ok == -1) {
+               if (errno != ENOMEDIUM)
+                       perror("IOCTL: DKIOCCDREADTOC");
+               ciw->changed = true;
+               uae_sem_post(&ciw->cda.sub_sem);
+               return 0;
+       }
+       if (rtoc.bufferLength > sizeof(buf))
+               rtoc.bufferLength = sizeof(buf);
+
+       uae_sem_post(&ciw->cda.sub_sem);
+       return 1;
+#elif defined(__linux__)
+       if (!open_createfile(ciw, 0))
+               return 0;
+       uae_sem_wait(&ciw->cda.sub_sem);
+
+       int status = ioctl(ciw->fd, CDROM_DRIVE_STATUS, CDSL_NONE);
+       if (status == -1)
+       {
+               perror("IOCTL: CDROM_DRIVE_STATUS");
+       }
+       if (status != CDS_DISC_OK)
+       {
+               ciw->changed = true;
+               uae_sem_post(&ciw->cda.sub_sem);
+               return 0;
+       }
+
+       ciw->di.bytespersector = 2048;
+
+       uae_sem_post(&ciw->cda.sub_sem);
+       return 1;
+#else
+  //FreeBSD not implemented
+  return 0;
+#endif
+}
+
+static int ismedia(struct dev_info_ioctl* ciw, int unitnum)
+{
+       return fetch_geometry(ciw, unitnum, &ciw->di);
+}
+
+static int eject(int unitnum, bool eject)
+{
+    struct dev_info_ioctl* ciw = unitisopen(unitnum);
+
+    if (!ciw)
+        return 0;
+    if (!unitisopen(unitnum))
+        return 0;
+    ciw_cdda_stop(&ciw->cda);
+    if (!open_createfile(ciw, 0))
+        return 0;
+    int ret = 0;
+#ifdef UAE_HOST_DARWIN
+    if (eject) {
+        if (ioctl(ciw->fd, DKIOCEJECT, 0) < 0)
+            ret = 1;
+    }
+#elif defined(__linux__)
+    if (ioctl(ciw->fd, eject ? CDROMEJECT : CDROMCLOSETRAY, 0) < 0) {
+        ret = 1;
+    }
+#else
+    //FreeBSD not implemented
+#endif
+    return ret;
+}
+
+static int ioctl_command_toc2(int unitnum, struct cd_toc_head* tocout, bool hide_errors)
+{
+       struct dev_info_ioctl* ciw = unitisopen(unitnum);
+       if (!ciw)
+               return 0;
+
+#ifdef UAE_HOST_DARWIN
+       struct cd_toc_head* th = &ciw->di.toc;
+       struct cd_toc* t = th->toc;
+
+       if (!unitisopen(unitnum)) {
+               return 0;
+       }
+       if (!open_createfile(ciw, 0)) {
+               return 0;
+       }
+
+       DASessionRef session = DASessionCreate(kCFAllocatorDefault);
+       if (!session) {
+               return 0;
+       }
+
+       char bsd_name[64];
+       strncpy(bsd_name, ciw->devname, sizeof(bsd_name));
+       bsd_name[sizeof(bsd_name) - 1] = '\0';
+       if (!strncmp(bsd_name, "/dev/rdisk", 10)) {
+               snprintf(bsd_name, sizeof(bsd_name), "/dev/disk%s", ciw->devname + 10);
+       }
+
+       DADiskRef disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, bsd_name);
+       if (!disk) {
+               CFRelease(session);
+               return 0;
+       }
+       io_service_t media = DADiskCopyIOMedia(disk);
+       if (!media) {
+               CFRelease(disk);
+               CFRelease(session);
+               return 0;
+       }
+       CFTypeRef tocDataRef = IORegistryEntryCreateCFProperty(media, CFSTR("TOC"), kCFAllocatorDefault, 0);
+       if (!tocDataRef || CFGetTypeID(tocDataRef) != CFDataGetTypeID()) {
+               if (tocDataRef) CFRelease(tocDataRef);
+               IOObjectRelease(media);
+               CFRelease(disk);
+               CFRelease(session);
+               return 0;
+       }
+
+       CFDataRef tocData = (CFDataRef)tocDataRef;
+       const UInt8* bytes = CFDataGetBytePtr(tocData);
+       CDTOC* toc = (CDTOC*)bytes;
+       UInt32 count = CDTOCGetDescriptorCount(toc);
+
+       if (count == 0) {
+               CFRelease(tocDataRef);
+               IOObjectRelease(media);
+               CFRelease(disk);
+               CFRelease(session);
+               return 0;
+       }
+
+       memset(th, 0, sizeof(struct cd_toc_head));
+       memset(ciw->trackmode, 0xff, sizeof(ciw->trackmode));
+
+       int first_track = 99;
+       int last_track = 0;
+       int leadout_lsn = 0;
+       struct {
+               int present;
+               uae_u8 adr;
+               uae_u8 control;
+               int lsn;
+       } tracks[100]{};
+
+       for (UInt32 i = 0; i < count; ++i) {
+               const CDTOCDescriptor& d = toc->descriptors[i];
+               if (d.point >= 1 && d.point <= 99) {
+                       int lsn = (int)CDConvertMSFToLBA(d.p);
+                       tracks[d.point].present = 1;
+                       tracks[d.point].adr = d.adr;
+                       tracks[d.point].control = d.control;
+                       tracks[d.point].lsn = lsn;
+                       if (d.point < first_track) first_track = d.point;
+                       if (d.point > last_track) last_track = d.point;
+               } else if (d.point == 0xA2) {
+                       leadout_lsn = (int)CDConvertMSFToLBA(d.p);
+               }
+       }
+       CFRelease(tocDataRef);
+       IOObjectRelease(media);
+       CFRelease(disk);
+       CFRelease(session);
+
+       if (last_track < first_track) {
+               return 0;
+       }
+
+       if (leadout_lsn == 0 && tracks[last_track].present)
+               leadout_lsn = tracks[last_track].lsn;
+
+       th->first_track = first_track;
+       th->last_track = last_track;
+       th->tracks = th->last_track - th->first_track + 1;
+       th->points = th->tracks + 3;
+       th->firstaddress = 0;
+       th->lastaddress = leadout_lsn;
+
+       t->adr = 1;
+       t->point = 0xa0;
+       t->track = th->first_track;
+       t++;
+
+       th->first_track_offset = 1;
+       for (int tr = th->first_track; tr <= th->last_track; ++tr) {
+               if (!tracks[tr].present)
+                       return 0;
+               t->adr = tracks[tr].adr;
+               t->control = tracks[tr].control;
+               t->point = t->track = tr;
+               t->paddress = tracks[tr].lsn;
+               t++;
+       }
+
+       th->last_track_offset = th->last_track;
+       t->adr = 1;
+       t->point = 0xa1;
+       t->track = th->last_track;
+       t->paddress = th->lastaddress;
+       t++;
+
+       t->adr = 1;
+       t->point = 0xa2;
+       t->paddress = th->lastaddress;
+       t++;
+
+       memcpy(tocout, th, sizeof(struct cd_toc_head));
+       for (int i = th->first_track_offset; i <= th->last_track_offset + 1; i++) {
+               uae_u32 addr;
+               uae_u32 msf;
+               t = &th->toc[i];
+               if (i <= th->last_track_offset) {
+                       write_log(_T("%2d: "), t->track);
+                       addr = t->paddress;
+                       msf = lsn2msf(addr);
+               } else {
+                       write_log(_T("    "));
+                       addr = th->toc[th->last_track_offset + 2].paddress;
+                       msf = lsn2msf(addr);
+               }
+               write_log(_T("%7d %02d:%02d:%02d"),
+                       addr, (msf >> 16) & 0x7fff, (msf >> 8) & 0xff, (msf >> 0) & 0xff);
+               if (i <= th->last_track_offset) {
+                       write_log(_T(" %s %x"),
+                               (t->control & 4) ? _T("DATA    ") : _T("CDA     "), t->control);
+               }
+               write_log(_T("\n"));
+       }
+       return 1;
+#elif defined(__linux__)
+       int len;
+       int i;
+       struct cd_toc_head* th = &ciw->di.toc;
+       struct cd_toc* t = th->toc;
+       int cnt = 3;
+       memset(ciw->trackmode, 0xff, sizeof(ciw->trackmode));
+       struct cdrom_tochdr tochdr;
+       struct cdrom_tocentry tocentry;
+
+       if (!unitisopen(unitnum))
+               return 0;
+
+       if (!open_createfile(ciw, 0))
+               return 0;
+       while (cnt-- > 0) {
+               if (ioctl(ciw->fd, CDROMREADTOCHDR, &tochdr) == -1) {
+                       int err = errno;
+                       if (!hide_errors || (hide_errors && err == ENOMEDIUM)) {
+                               if (unix_ioctl_error(ciw, unitnum, _T("CDROMREADTOCHDR")) < 0)
+                                       continue;
+                       }
+                       return 0;
+               }
+               break;
+       }
+
+       memset(th, 0, sizeof(struct cd_toc_head));
+       th->first_track = tochdr.cdth_trk0;
+       th->last_track = tochdr.cdth_trk1;
+       th->tracks = th->last_track - th->first_track + 1;
+       th->points = th->tracks + 3;
+       th->firstaddress = 0;
+
+       // Read the leadout entry to get lastaddress
+       tocentry.cdte_track = CDROM_LEADOUT;
+       tocentry.cdte_format = CDROM_MSF;
+       if (ioctl(ciw->fd, CDROMREADTOCENTRY, &tocentry) == -1) {
+               return 0;
+       }
+       th->lastaddress = msf2lsn((tocentry.cdte_addr.msf.minute << 16) | (tocentry.cdte_addr.msf.second << 8) |
+               (tocentry.cdte_addr.msf.frame << 0));
+
+       t->adr = 1;
+       t->point = 0xa0;
+       t->track = th->first_track;
+       t++;
+
+       th->first_track_offset = 1;
+       for (i = th->first_track; i <= th->last_track; i++) {
+               tocentry.cdte_track = i;
+               tocentry.cdte_format = CDROM_MSF;
+               if (ioctl(ciw->fd, CDROMREADTOCENTRY, &tocentry) == -1) {
+                       return 0;
+               }
+               t->adr = tocentry.cdte_adr;
+               t->control = tocentry.cdte_ctrl;
+               t->paddress = msf2lsn((tocentry.cdte_addr.msf.minute << 16) | (tocentry.cdte_addr.msf.second << 8) |
+                       (tocentry.cdte_addr.msf.frame << 0));
+               t->point = t->track = i;
+               t++;
+       }
+
+       th->last_track_offset = th->last_track;
+       t->adr = 1;
+       t->point = 0xa1;
+       t->track = th->last_track;
+       t->paddress = th->lastaddress;
+       t++;
+
+       t->adr = 1;
+       t->point = 0xa2;
+       t->paddress = th->lastaddress;
+       t++;
+
+       for (i = th->first_track_offset; i <= th->last_track_offset + 1; i++) {
+               uae_u32 addr;
+               uae_u32 msf;
+               t = &th->toc[i];
+               if (i <= th->last_track_offset) {
+                       write_log(_T("%2d: "), t->track);
+                       addr = t->paddress;
+                       msf = lsn2msf(addr);
+               }
+               else {
+                       write_log(_T("    "));
+                       addr = th->toc[th->last_track_offset + 2].paddress;
+                       msf = lsn2msf(addr);
+               }
+               write_log(_T("%7d %02d:%02d:%02d"),
+                       addr, (msf >> 16) & 0x7fff, (msf >> 8) & 0xff, (msf >> 0) & 0xff);
+               if (i <= th->last_track_offset) {
+                       write_log(_T(" %s %x"),
+                               (t->control & 4) ? _T("DATA    ") : _T("CDA     "), t->control);
+               }
+               write_log(_T("\n"));
+       }
+
+       memcpy(tocout, th, sizeof(struct cd_toc_head));
+       return 1;
+#else
+  //FreeBSD not implemented
+  return 0;
+#endif
+}
+static int ioctl_command_toc(int unitnum, struct cd_toc_head* tocout)
+{
+       return ioctl_command_toc2(unitnum, tocout, false);
+}
+
+static void update_device_info(int unitnum)
+{
+       struct dev_info_ioctl* ciw = unitcheck(unitnum);
+       if (!ciw)
+               return;
+       struct device_info* di = &ciw->di;
+       di->bus = unitnum;
+       di->target = 0;
+       di->lun = 0;
+       di->media_inserted = 0;
+       di->bytespersector = 2048;
+       strncpy(di->mediapath, ciw->drvletter, sizeof(ciw->drvletter) - 1);
+       if (fetch_geometry(ciw, unitnum, di)) { // || ioctl_command_toc (unitnum))
+               di->media_inserted = 1;
+       }
+       if (ciw->changed || di->media_inserted) {
+               ioctl_command_toc2(unitnum, &di->toc, true);
+               ciw->changed = false;
+       }
+       di->removable = ciw->type == DRIVE_CDROM ? 1 : 0;
+       di->write_protected = ciw->type == DRIVE_CDROM ? 1 : 0;
+       di->type = ciw->type == DRIVE_CDROM ? INQ_ROMD : INQ_DASD;
+       di->unitnum = unitnum + 1;
+       _tcscpy(di->label, ciw->drvlettername);
+       di->backend = _T("IOCTL");
+}
+
+static void trim(TCHAR* s)
+{
+       while (s[0] != '\0' && s[_tcslen(s) - 1] == ' ')
+               s[_tcslen(s) - 1] = 0;
+}
+
+/* open device level access to cd rom drive */
+static int sys_cddev_open(struct dev_info_ioctl* ciw, int unitnum)
+{
+       ciw->cda.cdda_volume[0] = 0x7fff;
+       ciw->cda.cdda_volume[1] = 0x7fff;
+#ifdef UAE_HOST_DARWIN
+       uint32_t blocksize = 0;
+#endif
+       memset(&ciw->di, 0, sizeof(ciw->di));
+       ciw->tempbuffer = (unsigned char*)malloc(IOCTL_DATA_BUFFER);
+       if (!ciw->tempbuffer) {
+               write_log("IOCTL: Failed to allocate buffer\n");
+               return 1;
+       }
+
+       memset(ciw->di.vendorid, 0, sizeof(ciw->di.vendorid));
+       memset(ciw->di.productid, 0, sizeof(ciw->di.productid));
+       memset(ciw->di.revision, 0, sizeof(ciw->di.revision));
+       _tcscpy(ciw->di.vendorid, _T("UAE"));
+       snprintf(ciw->di.productid, sizeof(ciw->di.productid), "SCSI CD%d IMG", unitnum);
+       _tcscpy(ciw->di.revision, _T("0.2"));
+
+       if (!open_createfile(ciw, 0)) {
+               write_log("IOCTL: Failed to open '%s'\n", ciw->devname);
+               goto error;
+       }
+
+#ifdef UAE_HOST_DARWIN
+       if (ioctl(ciw->fd, DKIOCGETBLOCKSIZE, &blocksize) == 0 && blocksize > 0)
+               ciw->di.bytespersector = (int)blocksize;
+       else
+               ciw->di.bytespersector = 2048;
+#elif defined(__linux__)
+       struct hd_driveid id;
+       if (ioctl(ciw->fd, HDIO_GET_IDENTITY, &id) == 0) {
+               strncpy(ciw->di.vendorid, (const char*)id.model, sizeof(ciw->di.vendorid) - 1);
+               strncpy(ciw->di.productid, (const char*)id.serial_no, sizeof(ciw->di.productid) - 1);
+               strncpy(ciw->di.revision, (const char*)id.fw_rev, sizeof(ciw->di.revision) - 1);
+       }
+#else
+#endif
+
+       write_log(_T("IOCTL: sys_cddev_open device '%s' (%s/%s/%s) opened successfully (unit=%d,media=%d,fd=%d)\n"),
+               ciw->devname, ciw->di.vendorid, ciw->di.productid, ciw->di.revision,
+               unitnum, ciw->di.media_inserted, ciw->fd);
+       if (!_tcsicmp(ciw->di.vendorid, _T("iomega")) && !_tcsicmp(ciw->di.productid, _T("rrd"))) {
+               write_log(_T("Device blacklisted\n"));
+               goto error;
+       }
+       uae_sem_init(&ciw->cda.sub_sem, 0, 1);
+       uae_sem_init(&ciw->cda.sub_sem2, 0, 1);
+       ioctl_command_stop(unitnum);
+       update_device_info(unitnum);
+       write_log(_T("IOCTL: sys_cddev_open update_device_info completed.\n"));
+       ciw->open = true;
+       return 0;
+
+error:
+       write_log(_T("IOCTL: sys_cddev_open error for unit %d\n"), unitnum);
+
+       free(ciw->tempbuffer);
+       ciw->tempbuffer = NULL;
+       if (ciw->fd != -1) close(ciw->fd);
+       ciw->fd = -1;
+       return -1;
+}
+
+/* close device handle */
+static void sys_cddev_close(struct dev_info_ioctl* ciw, int unitnum) {
+       if (ciw->open == false)
+               return;
+       write_log(_T("IOCTL: sys_cddev_close called for unit %d\n"), unitnum);
+       ciw_cdda_stop(&ciw->cda);
+       close_createfile(ciw);
+       free(ciw->tempbuffer);
+       ciw->tempbuffer = NULL;
+       uae_sem_destroy(&ciw->cda.sub_sem);
+       uae_sem_destroy(&ciw->cda.sub_sem2);
+       ciw->open = false;
+       write_log(_T("IOCTL: device '%s' closed\n"), ciw->devname);
+}
+
+static int open_device(int unitnum, const char* ident, int flags)
+{
+       struct dev_info_ioctl* ciw = NULL;
+       if (ident && ident[0]) {
+#ifdef UAE_HOST_DARWIN
+               char ident_buf[64];
+               const char* ident_match1 = ident;
+               const char* ident_match2 = ident;
+               if (!strncmp(ident, "/dev/rdisk", 10)) {
+                       snprintf(ident_buf, sizeof(ident_buf), "/dev/disk%s", ident + 10);
+                       ident_match2 = ident_buf;
+               } else if (!strncmp(ident, "/dev/disk", 9)) {
+                       snprintf(ident_buf, sizeof(ident_buf), "/dev/rdisk%s", ident + 9);
+                       ident_match2 = ident_buf;
+               }
+#endif
+               for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
+                       ciw = &ciw32[i];
+                       if (unittable[i] == 0 && ciw->drvletter[0] != 0) {
+#ifdef UAE_HOST_DARWIN
+                               if (!strcmp(ciw->drvlettername, ident_match1) || !strcmp(ciw->drvlettername, ident_match2)) {
+#else
+                               if (!strcmp(ciw->drvlettername, ident)) {
+#endif
+                                       unittable[unitnum] = i + 1;
+                                       if (sys_cddev_open(ciw, unitnum) == 0)
+                                               return 1;
+                                       unittable[unitnum] = 0;
+                                       return 0;
+                               }
+                       }
+               }
+               return 0;
+       }
+       ciw = &ciw32[unitnum];
+       for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
+               if (unittable[i] == unitnum + 1)
+                       return 0;
+       }
+       if (ciw->drvletter[0] == 0)
+               return 0;
+       unittable[unitnum] = unitnum + 1;
+       if (sys_cddev_open(ciw, unitnum) == 0)
+               return 1;
+       unittable[unitnum] = 0;
+       blkdev_cd_change(unitnum, ciw->drvlettername);
+       return 0;
+}
+
+static void close_device(int unitnum)
+{
+       struct dev_info_ioctl* ciw = unitcheck(unitnum);
+       if (!ciw)
+               return;
+       sys_cddev_close(ciw, unitnum);
+       blkdev_cd_change(unitnum, ciw->drvlettername);
+       unittable[unitnum] = 0;
+}
+
+static int total_devices;
+
+static void close_bus(void)
+{
+       if (!bus_open) {
+               write_log(_T("IOCTL close_bus() when already closed!\n"));
+               return;
+       }
+       total_devices = 0;
+       for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
+               sys_cddev_close(&ciw32[i], i);
+               memset(&ciw32[i], 0, sizeof(struct dev_info_ioctl));
+               ciw32[i].fd = -1;
+               unittable[i] = 0;
+       }
+       bus_open = 0;
+       uae_sem_destroy(&play_sem);
+       write_log(_T("IOCTL driver closed.\n"));
+}
+
+static int open_bus(int flags)
+{
+       if (bus_open) {
+               write_log(_T("IOCTL open_bus() more than once!\n"));
+               return 1;
+       }
+       write_log(_T("IOCTL open_bus() called with flags=%d\n"), flags);
+       total_devices = 0;
+       for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
+               memset(&ciw32[i], 0, sizeof(struct dev_info_ioctl));
+               ciw32[i].fd = -1;
+               unittable[i] = 0;
+       }
+       auto cd_drives = get_cd_drives();
+       if (!cd_drives.empty()) {
+               for (const auto& drive : cd_drives) {
+                       if (total_devices >= MAX_TOTAL_SCSI_DEVICES)
+                               break;
+                       const char *open_path = drive.c_str();
+                       write_log(_T("IOCTL evaluating drive: %s\n"), open_path);
+#ifdef UAE_HOST_DARWIN
+                       char open_buf[64];
+                       if (!strncmp(open_path, "/dev/rdisk", 10)) {
+                               snprintf(open_buf, sizeof(open_buf), "/dev/disk%s", open_path + 10);
+                               open_path = open_buf;
+                       }
+#endif
+                       int fd = open(open_path, O_RDONLY | O_NONBLOCK);
+#ifdef UAE_HOST_DARWIN
+                       if (fd == -1) {
+                               fd = open(open_path, O_RDONLY);
+                       }
+
+                       if (fd == -1 && !strncmp(open_path, "/dev/disk", 9)) {
+                               snprintf(open_buf, sizeof(open_buf), "/dev/rdisk%s", open_path + 9);
+                               fd = open(open_buf, O_RDONLY | O_NONBLOCK);
+                               if (fd == -1) {
+                                       fd = open(open_buf, O_RDONLY);
+                               }
+                               if (fd != -1) {
+                                       open_path = open_buf;
+                               }
+                       }
+#endif
+                       if (fd != -1) {
+                               struct stat st;
+                               if (fstat(fd, &st) == 0) {
+                                       bool is_block = S_ISBLK(st.st_mode);
+#ifdef UAE_HOST_DARWIN
+                                       bool is_char = S_ISCHR(st.st_mode);
+#endif
+                                       if (is_block
+#ifdef UAE_HOST_DARWIN
+                                               || is_char
+#endif
+                                       ) {
+                                               strncpy(ciw32[total_devices].drvletter, open_path, sizeof(ciw32[total_devices].drvletter) - 1);
+                                               strncpy(ciw32[total_devices].drvlettername, open_path, sizeof(ciw32[total_devices].drvlettername) - 1);
+                                               strncpy(ciw32[total_devices].devname, drive.c_str(), sizeof(ciw32[total_devices].devname) - 1);
+                                               ciw32[total_devices].type = DRIVE_CDROM;
+                                               ciw32[total_devices].di.bytespersector = 2048;
+                                               ciw32[total_devices].fd = -1;
+                                               total_devices++;
+                                       }
+                               }
+                               close(fd);
+                       }
+               }
+       } else {
+               write_log(_T("IOCTL get_cd_drives() returned no drives.\n"));
+       }
+       bus_open = 1;
+       uae_sem_init(&play_sem, 0, 1);
+       write_log(_T("IOCTL driver open, %d devices.\n"), total_devices);
+       return total_devices;
+}
+
+static int ioctl_ismedia(int unitnum, int quick)
+{
+       struct dev_info_ioctl* ciw = unitisopen(unitnum);
+       if (!ciw)
+               return -1;
+       if (quick) {
+               return ciw->di.media_inserted;
+       }
+       update_device_info(unitnum);
+       return ismedia(ciw, unitnum);
+}
+
+static struct device_info* info_device(int unitnum, struct device_info* di, int quick, int session)
+{
+       struct dev_info_ioctl* ciw = unitcheck(unitnum);
+       if (!ciw)
+               return 0;
+       if (!quick)
+               update_device_info(unitnum);
+       ciw->di.open = ciw->open;
+       memcpy(di, &ciw->di, sizeof(struct device_info));
+       return di;
+}
+
+static int ioctl_scsiemu(int unitnum, uae_u8* cmd)
+{
+       uae_u8 c = cmd[0];
+       if (c == 0x1b) {
+               int mode = cmd[4] & 3;
+               if (mode == 2)
+                       eject(unitnum, true);
+               else if (mode == 3)
+                       eject(unitnum, false);
+               return 1;
+       }
+       return -1;
+}
+
+struct device_functions devicefunc_scsi_ioctl = {
+       _T("IOCTL"),
+       open_bus, close_bus, open_device, close_device, info_device,
+       0, 0, 0,
+       ioctl_command_pause, ioctl_command_stop, ioctl_command_play, ioctl_command_volume, ioctl_command_qcode,
+       ioctl_command_toc, ioctl_command_read, ioctl_command_rawread, ioctl_command_write,
+       0, ioctl_ismedia, ioctl_scsiemu
+};
+
+#endif /* WITH_SCSI_IOCTL */
diff --git a/od-unix/blkdev_unix_scsi_macos.cpp b/od-unix/blkdev_unix_scsi_macos.cpp
new file mode 100644 (file)
index 0000000..9bcec9f
--- /dev/null
@@ -0,0 +1,602 @@
+/*
+ * UAE - The Un*x Amiga Emulator
+ *
+ * macOS SCSITaskLib native SCSI passthrough
+ *
+ * Copyright 2026 WinUAE contributors
+ */
+
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#ifdef WITH_SCSI_SPTI
+
+#include "options.h"
+#include "threaddep/thread.h"
+#include "blkdev.h"
+#include "execio.h"
+#include "gui.h"
+
+#include <algorithm>
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/scsi/SCSITaskLib.h>
+
+#define INQUIRY_SIZE 36
+
+struct dev_info_scsi_macos {
+       io_service_t service;
+       UInt64 registry_id;
+       bool open;
+       bool exclusive;
+       bool enabled;
+       bool removable;
+       int type;
+       TCHAR label[MAX_DPATH];
+       uae_u8 inquirydata[INQUIRY_SIZE];
+       uae_u8 sense[sizeof(SCSI_Sense_Data)];
+       int senselen;
+       uae_u8 *scsibuf;
+       SCSITaskDeviceInterface **interface;
+       struct device_info di;
+};
+
+static struct dev_info_scsi_macos dev_info[MAX_TOTAL_SCSI_DEVICES];
+static int unittable[MAX_TOTAL_SCSI_DEVICES];
+static int total_devices;
+static int bus_open;
+static uae_sem_t scsi_sem;
+
+static void release_interface(struct dev_info_scsi_macos *di)
+{
+       if (!di || !di->interface) {
+               return;
+       }
+       if (di->exclusive) {
+               (*di->interface)->ReleaseExclusiveAccess(di->interface);
+               di->exclusive = false;
+       }
+       (*di->interface)->Release(di->interface);
+       di->interface = NULL;
+}
+
+static bool create_interface(io_service_t service, SCSITaskDeviceInterface ***out)
+{
+       IOCFPlugInInterface **plugin = NULL;
+       SCSITaskDeviceInterface **interface = NULL;
+       SInt32 score = 0;
+
+       if (!out) {
+               return false;
+       }
+       *out = NULL;
+       IOReturn kr = IOCreatePlugInInterfaceForService(service,
+               kIOSCSITaskDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &score);
+       if (kr != kIOReturnSuccess || !plugin) {
+               return false;
+       }
+       HRESULT hr = (*plugin)->QueryInterface(plugin,
+               CFUUIDGetUUIDBytes(kIOSCSITaskDeviceInterfaceID), (LPVOID *)&interface);
+       (*plugin)->Release(plugin);
+       if (hr || !interface) {
+               return false;
+       }
+       *out = interface;
+       return true;
+}
+
+static bool open_interface(struct dev_info_scsi_macos *di, bool exclusive)
+{
+       if (!di) {
+               return false;
+       }
+       if (di->interface) {
+               return true;
+       }
+       if (!create_interface(di->service, &di->interface)) {
+               return false;
+       }
+       if (exclusive) {
+               IOReturn kr = (*di->interface)->ObtainExclusiveAccess(di->interface);
+               if (kr != kIOReturnSuccess) {
+                       write_log(_T("SCSITaskLib: exclusive access failed for '%s': 0x%08x\n"), di->label, kr);
+                       release_interface(di);
+                       return false;
+               }
+               di->exclusive = true;
+       }
+       return true;
+}
+
+static int normalized_cdb_size(int cmdlen)
+{
+       if (cmdlen <= 6) {
+               return kSCSICDBSize_6Byte;
+       }
+       if (cmdlen <= 10) {
+               return kSCSICDBSize_10Byte;
+       }
+       if (cmdlen <= 12) {
+               return kSCSICDBSize_12Byte;
+       }
+       return kSCSICDBSize_16Byte;
+}
+
+static int execute_task(struct dev_info_scsi_macos *di, const uae_u8 *cmd, int cmdlen,
+       uae_u8 *data, int datalen, int direction, uae_u8 *sense, int senselen,
+       uae_u8 *statusp, int *actualp, int timeout_ms)
+{
+       if (!di || !di->interface || !cmd || cmdlen <= 0 || cmdlen > kSCSICDBSize_Maximum) {
+               return -1;
+       }
+
+       SCSITaskInterface **task = (*di->interface)->CreateSCSITask(di->interface);
+       if (!task) {
+               return -1;
+       }
+
+       UInt8 cdb[kSCSICDBSize_Maximum];
+       memset(cdb, 0, sizeof(cdb));
+       memcpy(cdb, cmd, cmdlen);
+
+       IOReturn kr = (*task)->SetTaskAttribute(task, kSCSITask_SIMPLE);
+       if (kr == kIOReturnSuccess) {
+               kr = (*task)->SetCommandDescriptorBlock(task, cdb, normalized_cdb_size(cmdlen));
+       }
+       if (kr == kIOReturnSuccess) {
+               kr = (*task)->SetTimeoutDuration(task, timeout_ms > 0 ? timeout_ms : 5000);
+       }
+       if (kr == kIOReturnSuccess) {
+               SCSITaskSGElement sg;
+               memset(&sg, 0, sizeof(sg));
+               UInt8 transfer_direction = kSCSIDataTransfer_NoDataTransfer;
+               if (datalen > 0 && data) {
+                       sg.address = (IOVirtualAddress)data;
+                       sg.length = datalen;
+                       transfer_direction = direction;
+               }
+               kr = (*task)->SetScatterGatherEntries(task, datalen > 0 && data ? &sg : NULL,
+                       datalen > 0 && data ? 1 : 0, datalen > 0 && data ? datalen : 0, transfer_direction);
+       }
+
+       SCSI_Sense_Data sense_data;
+       memset(&sense_data, 0, sizeof(sense_data));
+       SCSITaskStatus task_status = kSCSITaskStatus_No_Status;
+       UInt64 realized = 0;
+       if (kr == kIOReturnSuccess) {
+               kr = (*task)->ExecuteTaskSync(task, &sense_data, &task_status, &realized);
+       }
+
+       if (sense && senselen > 0) {
+               int copylen = std::min(senselen, (int)sizeof(sense_data));
+               memcpy(sense, &sense_data, copylen);
+       }
+       if (statusp) {
+               *statusp = (uae_u8)task_status;
+       }
+       if (actualp) {
+               *actualp = (int)std::min<UInt64>(realized, datalen > 0 ? (UInt64)datalen : 0);
+       }
+
+       (*task)->Release(task);
+       return kr == kIOReturnSuccess ? 0 : -1;
+}
+
+static bool inquiry_device(struct dev_info_scsi_macos *di, uae_u8 *statusp)
+{
+       uae_u8 cmd[6] = { 0x12, 0, 0, 0, INQUIRY_SIZE, 0 };
+       int actual = 0;
+
+       memset(di->inquirydata, 0, sizeof(di->inquirydata));
+       if (execute_task(di, cmd, sizeof(cmd), di->inquirydata, INQUIRY_SIZE,
+               kSCSIDataTransfer_FromTargetToInitiator, di->sense, sizeof(di->sense),
+               statusp, &actual, 5000) < 0) {
+               return false;
+       }
+       return (!statusp || *statusp == 0) && actual >= 5;
+}
+
+static bool trim_copy(char *dst, size_t dstsize, const uae_u8 *src, size_t len)
+{
+       if (!dst || dstsize == 0) {
+               return false;
+       }
+       while (len > 0 && src[len - 1] == ' ') {
+               len--;
+       }
+       size_t start = 0;
+       while (start < len && src[start] == ' ') {
+               start++;
+       }
+       len -= start;
+       if (len >= dstsize) {
+               len = dstsize - 1;
+       }
+       memcpy(dst, src + start, len);
+       dst[len] = 0;
+       return len > 0;
+}
+
+static void inquiry_label(TCHAR *dst, size_t dstlen, const char *fallback, const uae_u8 *inq)
+{
+       char vendor[9] = { 0 };
+       char product[17] = { 0 };
+       char revision[5] = { 0 };
+       char label[MAX_DPATH];
+
+       trim_copy(vendor, sizeof(vendor), inq + 8, 8);
+       trim_copy(product, sizeof(product), inq + 16, 16);
+       trim_copy(revision, sizeof(revision), inq + 32, 4);
+       if (vendor[0] || product[0]) {
+               snprintf(label, sizeof(label), "%s %s %s", vendor, product, revision);
+       } else {
+               snprintf(label, sizeof(label), "%s", fallback);
+       }
+       TCHAR *tlabel = au(label);
+       _tcsncpy(dst, tlabel, dstlen - 1);
+       dst[dstlen - 1] = 0;
+       xfree(tlabel);
+}
+
+static struct dev_info_scsi_macos *unitcheck(int unitnum)
+{
+       if (unitnum < 0 || unitnum >= MAX_TOTAL_SCSI_DEVICES) {
+               return NULL;
+       }
+       if (unittable[unitnum] <= 0) {
+               return NULL;
+       }
+       return &dev_info[unittable[unitnum] - 1];
+}
+
+static struct dev_info_scsi_macos *unitisopen(int unitnum)
+{
+       struct dev_info_scsi_macos *di = unitcheck(unitnum);
+       if (!di || !di->open) {
+               return NULL;
+       }
+       return di;
+}
+
+static void update_device_info(int unitnum)
+{
+       struct dev_info_scsi_macos *dmac = unitcheck(unitnum);
+       if (!dmac) {
+               return;
+       }
+       struct device_info *di = &dmac->di;
+       memset(di, 0, sizeof(*di));
+       _tcscpy(di->label, dmac->label);
+       _tcscpy(di->mediapath, dmac->label);
+       di->type = dmac->type;
+       di->media_inserted = dmac->open ? 1 : -1;
+       di->removable = dmac->removable;
+       di->write_protected = true;
+       di->bytespersector = dmac->type == INQ_ROMD ? 2048 : 512;
+       di->trackspercylinder = 1;
+       di->sectorspertrack = 1;
+       di->cylinders = 0;
+       di->bus = 0;
+       di->target = unitnum;
+       di->lun = 0;
+       di->unitnum = unitnum + 1;
+       di->backend = _T("SCSITaskLib");
+}
+
+static bool add_service(io_service_t service)
+{
+       if (total_devices >= MAX_TOTAL_SCSI_DEVICES) {
+               return false;
+       }
+
+       UInt64 registry_id = 0;
+       if (IORegistryEntryGetRegistryEntryID(service, &registry_id) != kIOReturnSuccess) {
+               registry_id = 0;
+       }
+       for (int i = 0; i < total_devices; i++) {
+               if (registry_id && dev_info[i].registry_id == registry_id) {
+                       return false;
+               }
+       }
+
+       SCSITaskDeviceInterface **test_interface = NULL;
+       if (!create_interface(service, &test_interface)) {
+               return false;
+       }
+       (*test_interface)->Release(test_interface);
+
+       io_name_t name;
+       if (IORegistryEntryGetName(service, name) != kIOReturnSuccess) {
+               strcpy(name, "SCSITask device");
+       }
+
+       struct dev_info_scsi_macos *di = &dev_info[total_devices];
+       memset(di, 0, sizeof(*di));
+       di->service = service;
+       IOObjectRetain(di->service);
+       di->registry_id = registry_id;
+       di->enabled = true;
+       di->type = INQ_DASD;
+       TCHAR *tname = au(name);
+       _tcsncpy(di->label, tname, sizeof(di->label) / sizeof(TCHAR) - 1);
+       xfree(tname);
+
+       write_log(_T("SCSITaskLib: unit %d '%s'\n"), total_devices, di->label);
+       total_devices++;
+       return true;
+}
+
+static void enumerate_class(const char *classname)
+{
+       CFMutableDictionaryRef matching = IOServiceMatching(classname);
+       if (!matching) {
+               return;
+       }
+       io_iterator_t iterator = IO_OBJECT_NULL;
+       if (IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iterator) != kIOReturnSuccess) {
+               return;
+       }
+       io_service_t service;
+       while ((service = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
+               add_service(service);
+               IOObjectRelease(service);
+       }
+       IOObjectRelease(iterator);
+}
+
+static int open_macos_device2(struct dev_info_scsi_macos *di, int unitnum)
+{
+       if (!di || !di->enabled) {
+               return 0;
+       }
+       if (!di->scsibuf) {
+               di->scsibuf = xmalloc(uae_u8, DEVICE_SCSI_BUFSIZE);
+       }
+       if (!open_interface(di, true)) {
+               return 0;
+       }
+
+       uae_u8 status = 0;
+       if (inquiry_device(di, &status)) {
+               di->type = di->inquirydata[0] & 31;
+               di->removable = (di->inquirydata[1] & 0x80) != 0;
+               inquiry_label(di->label, sizeof(di->label) / sizeof(TCHAR), "SCSITask device", di->inquirydata);
+       } else {
+               write_log(_T("SCSITaskLib: inquiry failed for '%s' status=%02x\n"), di->label, status);
+       }
+       di->open = true;
+       update_device_info(unitnum);
+       return 1;
+}
+
+static void close_macos_device2(struct dev_info_scsi_macos *di)
+{
+       if (!di) {
+               return;
+       }
+       di->open = false;
+       release_interface(di);
+}
+
+static int open_macos_device(int unitnum, const TCHAR *ident, int flags)
+{
+       struct dev_info_scsi_macos *di = NULL;
+       if (ident && ident[0]) {
+               for (int i = 0; i < total_devices; i++) {
+                       di = &dev_info[i];
+                       if (!_tcsicmp(di->label, ident)) {
+                               unittable[unitnum] = i + 1;
+                               if (open_macos_device2(di, unitnum)) {
+                                       return 1;
+                               }
+                               unittable[unitnum] = 0;
+                               return 0;
+                       }
+               }
+               return 0;
+       }
+       if (unitnum >= total_devices) {
+               return 0;
+       }
+       for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
+               if (unittable[i] == unitnum + 1) {
+                       return 0;
+               }
+       }
+       di = &dev_info[unitnum];
+       unittable[unitnum] = unitnum + 1;
+       if (open_macos_device2(di, unitnum)) {
+               return 1;
+       }
+       unittable[unitnum] = 0;
+       return 0;
+}
+
+static void close_macos_device(int unitnum)
+{
+       struct dev_info_scsi_macos *di = unitisopen(unitnum);
+       if (!di) {
+               return;
+       }
+       close_macos_device2(di);
+       unittable[unitnum] = 0;
+}
+
+static struct device_info *info_macos_device(int unitnum, struct device_info *di, int quick, int session)
+{
+       struct dev_info_scsi_macos *dmac = unitcheck(unitnum);
+       if (!dmac) {
+               return NULL;
+       }
+       if (!quick) {
+               update_device_info(unitnum);
+       }
+       dmac->di.open = dmac->open;
+       memcpy(di, &dmac->di, sizeof(struct device_info));
+       return di;
+}
+
+static int exec_macos_direct(int unitnum, struct amigascsi *as)
+{
+       struct dev_info_scsi_macos *di = unitisopen(unitnum);
+       if (!di) {
+               return -1;
+       }
+
+       uae_u8 cmd[kSCSICDBSize_Maximum];
+       uae_u8 *scsi_datap = as->len ? as->data : NULL;
+       int datalen = as->len;
+       int cmdlen = std::min(as->cmd_len, (int)sizeof(cmd));
+       if (datalen > DEVICE_SCSI_BUFSIZE) {
+               datalen = DEVICE_SCSI_BUFSIZE;
+       }
+       memcpy(cmd, as->cmd, cmdlen);
+       if (datalen > 0 && scsi_datap) {
+               memcpy(di->scsibuf, scsi_datap, datalen);
+       }
+
+       const int direction = (as->flags & 1)
+               ? kSCSIDataTransfer_FromTargetToInitiator
+               : kSCSIDataTransfer_FromInitiatorToTarget;
+       uae_u8 status = 0;
+       int actual = 0;
+       int senselen = std::min((int)as->sense_len, (int)sizeof(di->sense));
+
+       uae_sem_wait(&scsi_sem);
+       int ret = execute_task(di, cmd, cmdlen, datalen > 0 ? di->scsibuf : NULL, datalen,
+               direction, di->sense, senselen, &status, &actual, 80 * 60 * 1000);
+       uae_sem_post(&scsi_sem);
+
+       if (datalen > 0 && scsi_datap && direction == kSCSIDataTransfer_FromTargetToInitiator) {
+               memcpy(scsi_datap, di->scsibuf, actual);
+       }
+
+       as->status = status;
+       as->cmdactual = ret < 0 ? 0 : as->cmd_len;
+       as->sactual = 0;
+       if (status) {
+               for (int i = 0; i < senselen; i++) {
+                       as->sensedata[i] = di->sense[i];
+                       as->sactual++;
+               }
+               as->actual = 0;
+               return IOERR_BadStatus;
+       }
+       if (ret < 0) {
+               as->actual = 0;
+               return IOERR_NotSpecified;
+       }
+       as->len = actual;
+       as->actual = actual;
+       for (int i = 0; i < senselen; i++) {
+               as->sensedata[i] = 0;
+       }
+       return 0;
+}
+
+static uae_u8 *exec_macos_out(int unitnum, uae_u8 *data, int len)
+{
+       struct amigascsi as;
+       memset(&as, 0, sizeof(as));
+       as.data = data;
+       as.len = 0;
+       memcpy(as.cmd, data, len);
+       as.cmd_len = len;
+       as.flags = 0;
+       if (exec_macos_direct(unitnum, &as) != 0) {
+               return NULL;
+       }
+       return data;
+}
+
+static uae_u8 *exec_macos_in(int unitnum, uae_u8 *data, int len, int *outlen)
+{
+       struct dev_info_scsi_macos *di = unitisopen(unitnum);
+       if (!di) {
+               return NULL;
+       }
+       struct amigascsi as;
+       memset(&as, 0, sizeof(as));
+       as.data = di->scsibuf;
+       as.len = DEVICE_SCSI_BUFSIZE;
+       memcpy(as.cmd, data, len);
+       as.cmd_len = len;
+       as.flags = 1;
+       as.sense_len = sizeof(as.sensedata);
+       if (exec_macos_direct(unitnum, &as) != 0 || as.actual <= 0) {
+               return NULL;
+       }
+       if (outlen) {
+               *outlen = std::min(*outlen, (int)as.actual);
+       }
+       return di->scsibuf;
+}
+
+static int macos_isatapi(int unitnum)
+{
+       return 0;
+}
+
+static int macos_ismedia(int unitnum, int quick)
+{
+       struct dev_info_scsi_macos *di = unitisopen(unitnum);
+       return di ? 1 : -1;
+}
+
+static int open_macos_bus(int flags)
+{
+       if (bus_open) {
+               write_log(_T("SCSITaskLib open_bus() more than once!\n"));
+               return 1;
+       }
+       total_devices = 0;
+       memset(dev_info, 0, sizeof(dev_info));
+       memset(unittable, 0, sizeof(unittable));
+       uae_sem_init(&scsi_sem, 0, 1);
+
+       enumerate_class("IOSCSITaskDevice");
+       enumerate_class("IOSCSIPeripheralDeviceNub");
+       enumerate_class("IOSCSIPrimaryCommandsDevice");
+       enumerate_class("IOSCSIMultimediaCommandsDevice");
+
+       bus_open = 1;
+       write_log(_T("SCSITaskLib driver open, %d devices.\n"), total_devices);
+       return total_devices;
+}
+
+static void close_macos_bus(void)
+{
+       if (!bus_open) {
+               write_log(_T("SCSITaskLib close_bus() when already closed!\n"));
+               return;
+       }
+       for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
+               close_macos_device2(&dev_info[i]);
+               xfree(dev_info[i].scsibuf);
+               dev_info[i].scsibuf = NULL;
+               if (dev_info[i].service) {
+                       IOObjectRelease(dev_info[i].service);
+                       dev_info[i].service = IO_OBJECT_NULL;
+               }
+               unittable[i] = 0;
+       }
+       total_devices = 0;
+       bus_open = 0;
+       uae_sem_destroy(&scsi_sem);
+       write_log(_T("SCSITaskLib driver closed.\n"));
+}
+
+struct device_functions devicefunc_scsi_spti = {
+       _T("SCSITaskLib"),
+       open_macos_bus, close_macos_bus, open_macos_device, close_macos_device, info_macos_device,
+       exec_macos_out, exec_macos_in, exec_macos_direct,
+       0, 0, 0, 0, 0,
+       0, 0, 0, 0,
+       macos_isatapi, macos_ismedia, 0
+};
+
+#endif /* WITH_SCSI_SPTI */
diff --git a/od-unix/blkdev_unix_sg.cpp b/od-unix/blkdev_unix_sg.cpp
new file mode 100644 (file)
index 0000000..ff03051
--- /dev/null
@@ -0,0 +1,684 @@
+/*
+ * UAE - The Un*x Amiga Emulator
+ *
+ * Linux SG_IO native SCSI passthrough
+ *
+ * Copyright 2026 WinUAE contributors
+ */
+
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#ifdef WITH_SCSI_SPTI
+
+#include "options.h"
+#include "threaddep/thread.h"
+#include "blkdev.h"
+#include "execio.h"
+#include "gui.h"
+
+#include <algorithm>
+#include <cerrno>
+#include <cctype>
+#include <cstdio>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <scsi/sg.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define INQUIRY_SIZE 36
+
+struct dev_info_sg {
+       int fd;
+       bool open;
+       bool readonly;
+       bool enabled;
+       bool removable;
+       bool isatapi;
+       int type;
+       char path[MAX_DPATH];
+       char realpath[MAX_DPATH];
+       TCHAR label[MAX_DPATH];
+       uae_u8 inquirydata[INQUIRY_SIZE];
+       uae_u8 sense[32];
+       int senselen;
+       uae_u8 *scsibuf;
+       struct device_info di;
+};
+
+static struct dev_info_sg dev_info[MAX_TOTAL_SCSI_DEVICES];
+static int unittable[MAX_TOTAL_SCSI_DEVICES];
+static int total_devices;
+static int bus_open;
+static uae_sem_t scgp_sem;
+
+static bool digits_only(const char *s)
+{
+       if (!s || !*s) {
+               return false;
+       }
+       while (*s) {
+               if (!isdigit((unsigned char)*s)) {
+                       return false;
+               }
+               s++;
+       }
+       return true;
+}
+
+static bool scsi_devname(const char *name)
+{
+       if (!name) {
+               return false;
+       }
+       if (!strncmp(name, "sg", 2) && digits_only(name + 2)) {
+               return true;
+       }
+       if (!strncmp(name, "sr", 2) && digits_only(name + 2)) {
+               return true;
+       }
+       if (!strncmp(name, "scd", 3) && digits_only(name + 3)) {
+               return true;
+       }
+       if (!strncmp(name, "st", 2) && digits_only(name + 2)) {
+               return true;
+       }
+       if (!strncmp(name, "nst", 3) && digits_only(name + 3)) {
+               return true;
+       }
+       return false;
+}
+
+static std::string resolved_path(const char *path)
+{
+       char tmp[PATH_MAX];
+       if (realpath(path, tmp)) {
+               return std::string(tmp);
+       }
+       return std::string(path);
+}
+
+static void append_if_new(std::vector<std::string> *paths, const std::string &path)
+{
+       const std::string real = resolved_path(path.c_str());
+       for (const std::string &p : *paths) {
+               if (resolved_path(p.c_str()) == real) {
+                       return;
+               }
+       }
+       paths->push_back(path);
+}
+
+static std::vector<std::string> candidate_paths()
+{
+       std::vector<std::string> paths;
+       DIR *dev = opendir("/dev");
+       if (dev) {
+               struct dirent *entry;
+               while ((entry = readdir(dev)) != NULL) {
+                       if (scsi_devname(entry->d_name)) {
+                               std::string path("/dev/");
+                               path += entry->d_name;
+                               append_if_new(&paths, path);
+                       }
+               }
+               closedir(dev);
+       }
+
+       DIR *byid = opendir("/dev/tape/by-id");
+       if (byid) {
+               struct dirent *entry;
+               while ((entry = readdir(byid)) != NULL) {
+                       if (entry->d_name[0] == '.') {
+                               continue;
+                       }
+                       std::string path("/dev/tape/by-id/");
+                       path += entry->d_name;
+                       append_if_new(&paths, path);
+               }
+               closedir(byid);
+       }
+
+       return paths;
+}
+
+static bool trim_copy(char *dst, size_t dstsize, const uae_u8 *src, size_t len)
+{
+       if (!dst || dstsize == 0) {
+               return false;
+       }
+       while (len > 0 && src[len - 1] == ' ') {
+               len--;
+       }
+       size_t start = 0;
+       while (start < len && src[start] == ' ') {
+               start++;
+       }
+       len -= start;
+       if (len >= dstsize) {
+               len = dstsize - 1;
+       }
+       memcpy(dst, src + start, len);
+       dst[len] = 0;
+       return len > 0;
+}
+
+static void inquiry_label(TCHAR *dst, size_t dstlen, const char *path, const uae_u8 *inq)
+{
+       char vendor[9] = { 0 };
+       char product[17] = { 0 };
+       char revision[5] = { 0 };
+       char label[MAX_DPATH];
+
+       trim_copy(vendor, sizeof(vendor), inq + 8, 8);
+       trim_copy(product, sizeof(product), inq + 16, 16);
+       trim_copy(revision, sizeof(revision), inq + 32, 4);
+       snprintf(label, sizeof(label), "%s %s %s (%s)", vendor, product, revision, path);
+       TCHAR *tlabel = au(label);
+       _tcsncpy(dst, tlabel, dstlen - 1);
+       dst[dstlen - 1] = 0;
+       xfree(tlabel);
+}
+
+static struct dev_info_sg *unitcheck(int unitnum)
+{
+       if (unitnum < 0 || unitnum >= MAX_TOTAL_SCSI_DEVICES) {
+               return NULL;
+       }
+       if (unittable[unitnum] <= 0) {
+               return NULL;
+       }
+       return &dev_info[unittable[unitnum] - 1];
+}
+
+static struct dev_info_sg *unitisopen(int unitnum)
+{
+       struct dev_info_sg *di = unitcheck(unitnum);
+       if (!di || !di->open) {
+               return NULL;
+       }
+       return di;
+}
+
+static int sg_exec_fd(int fd, uae_u8 *cmd, int cmdlen, uae_u8 *data, int datalen,
+       int direction, uae_u8 *sense, int senselen, uae_u8 *statusp, int *actualp, int timeout_ms)
+{
+       sg_io_hdr_t hdr;
+       memset(&hdr, 0, sizeof(hdr));
+       hdr.interface_id = 'S';
+       hdr.cmdp = cmd;
+       hdr.cmd_len = cmdlen;
+       hdr.dxferp = data;
+       hdr.dxfer_len = datalen;
+       hdr.dxfer_direction = datalen > 0 ? direction : SG_DXFER_NONE;
+       hdr.sbp = sense;
+       hdr.mx_sb_len = senselen;
+       hdr.timeout = timeout_ms > 0 ? timeout_ms : 5000;
+
+       if (sense && senselen > 0) {
+               memset(sense, 0, senselen);
+       }
+       if (ioctl(fd, SG_IO, &hdr) < 0) {
+               return -1;
+       }
+       if (statusp) {
+               *statusp = hdr.status;
+       }
+       if (actualp) {
+               *actualp = datalen - hdr.resid;
+       }
+       if ((hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK && hdr.status == 0) {
+               return -1;
+       }
+       return 0;
+}
+
+static bool inquiry_fd(int fd, uae_u8 *inq, uae_u8 *statusp)
+{
+       uae_u8 cmd[6] = { 0x12, 0, 0, 0, INQUIRY_SIZE, 0 };
+       uae_u8 sense[32];
+       int actual = 0;
+       memset(inq, 0, INQUIRY_SIZE);
+       if (sg_exec_fd(fd, cmd, sizeof(cmd), inq, INQUIRY_SIZE, SG_DXFER_FROM_DEV,
+               sense, sizeof(sense), statusp, &actual, 5000) < 0) {
+               return false;
+       }
+       return (!statusp || *statusp == 0) && actual >= 5;
+}
+
+static int open_path_rw(const char *path, bool *readonly)
+{
+       int fd = open(path, O_RDWR | O_NONBLOCK);
+       if (fd >= 0) {
+               if (readonly) {
+                       *readonly = false;
+               }
+               return fd;
+       }
+       fd = open(path, O_RDONLY | O_NONBLOCK);
+       if (fd >= 0 && readonly) {
+               *readonly = true;
+       }
+       return fd;
+}
+
+static bool add_device(const char *path)
+{
+       if (total_devices >= MAX_TOTAL_SCSI_DEVICES) {
+               return false;
+       }
+
+       std::string real = resolved_path(path);
+       for (int i = 0; i < total_devices; i++) {
+               if (!strcmp(dev_info[i].realpath, real.c_str())) {
+                       return false;
+               }
+       }
+
+       bool readonly = false;
+       int fd = open_path_rw(path, &readonly);
+       if (fd < 0) {
+               return false;
+       }
+
+       uae_u8 inq[INQUIRY_SIZE];
+       uae_u8 status = 0;
+       if (!inquiry_fd(fd, inq, &status)) {
+               close(fd);
+               return false;
+       }
+       close(fd);
+
+       struct dev_info_sg *di = &dev_info[total_devices];
+       memset(di, 0, sizeof(*di));
+       di->fd = -1;
+       di->enabled = true;
+       di->readonly = readonly;
+       di->type = inq[0] & 31;
+       di->removable = (inq[1] & 0x80) != 0;
+       di->isatapi = di->type == INQ_ROMD && (inq[2] & 7) == 0;
+       memcpy(di->inquirydata, inq, sizeof(di->inquirydata));
+       strncpy(di->path, path, sizeof(di->path) - 1);
+       strncpy(di->realpath, real.c_str(), sizeof(di->realpath) - 1);
+       inquiry_label(di->label, sizeof(di->label) / sizeof(TCHAR), path, inq);
+
+       write_log(_T("SG_IO: unit %d '%s' type=%d %s\n"),
+               total_devices, di->label, di->type, readonly ? _T("readonly") : _T("read/write"));
+       total_devices++;
+       return true;
+}
+
+static int mediacheck(struct dev_info_sg *di)
+{
+       if (!di || !di->open) {
+               return -1;
+       }
+       uae_u8 cmd[6] = { 0, 0, 0, 0, 0, 0 };
+       uae_u8 status = 0;
+       int actual = 0;
+       if (sg_exec_fd(di->fd, cmd, sizeof(cmd), NULL, 0, SG_DXFER_NONE,
+               di->sense, sizeof(di->sense), &status, &actual, 5000) < 0) {
+               return 0;
+       }
+       return status == 0 ? 1 : 0;
+}
+
+static void update_device_info(int unitnum)
+{
+       struct dev_info_sg *disg = unitcheck(unitnum);
+       if (!disg) {
+               return;
+       }
+       struct device_info *di = &disg->di;
+       memset(di, 0, sizeof(*di));
+       _tcscpy(di->label, disg->label);
+       TCHAR *path = au(disg->path);
+       _tcsncpy(di->mediapath, path, sizeof(di->mediapath) / sizeof(TCHAR) - 1);
+       xfree(path);
+       di->type = disg->type;
+       di->media_inserted = mediacheck(disg);
+       di->removable = disg->removable;
+       di->write_protected = disg->readonly;
+       di->bytespersector = disg->type == INQ_ROMD ? 2048 : 512;
+       di->trackspercylinder = 1;
+       di->sectorspertrack = 1;
+       di->cylinders = 0;
+       di->bus = 0;
+       di->target = unitnum;
+       di->lun = 0;
+       di->unitnum = unitnum + 1;
+       di->backend = _T("SG_IO");
+
+       if (disg->open && (disg->type == INQ_DASD || disg->type == INQ_ROMD)) {
+               uae_u8 cmd[10] = { 0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+               uae_u8 data[32];
+               uae_u8 status = 0;
+               int actual = 0;
+               if (sg_exec_fd(disg->fd, cmd, sizeof(cmd), data, sizeof(data), SG_DXFER_FROM_DEV,
+                       disg->sense, sizeof(disg->sense), &status, &actual, 5000) == 0 && status == 0 && actual >= 8) {
+                       di->bytespersector = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7];
+                       di->sectorspertrack = ((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]) + 1;
+                       di->cylinders = 1;
+               }
+       }
+}
+
+static int open_sg_device2(struct dev_info_sg *di, int unitnum)
+{
+       if (!di || !di->enabled) {
+               return 0;
+       }
+       if (!di->scsibuf) {
+               di->scsibuf = xmalloc(uae_u8, DEVICE_SCSI_BUFSIZE);
+       }
+       di->fd = open_path_rw(di->path, &di->readonly);
+       if (di->fd < 0) {
+               write_log(_T("SG_IO: failed to open '%s': %s\n"), di->label, strerror(errno));
+               return 0;
+       }
+       uae_u8 status = 0;
+       if (!inquiry_fd(di->fd, di->inquirydata, &status)) {
+               write_log(_T("SG_IO: inquiry failed for '%s'\n"), di->label);
+               close(di->fd);
+               di->fd = -1;
+               return 0;
+       }
+       di->open = true;
+       update_device_info(unitnum);
+       return 1;
+}
+
+static void close_sg_device2(struct dev_info_sg *di)
+{
+       if (!di || !di->open) {
+               return;
+       }
+       di->open = false;
+       if (di->fd >= 0) {
+               close(di->fd);
+               di->fd = -1;
+       }
+}
+
+static int open_sg_device(int unitnum, const TCHAR *ident, int flags)
+{
+       struct dev_info_sg *di = NULL;
+       if (ident && ident[0]) {
+               for (int i = 0; i < total_devices; i++) {
+                       di = &dev_info[i];
+                       TCHAR *path = au(di->path);
+                       bool match = !_tcsicmp(path, ident) || !_tcsicmp(di->label, ident);
+                       xfree(path);
+                       if (match) {
+                               unittable[unitnum] = i + 1;
+                               if (open_sg_device2(di, unitnum)) {
+                                       return 1;
+                               }
+                               unittable[unitnum] = 0;
+                               return 0;
+                       }
+               }
+               return 0;
+       }
+       if (unitnum >= total_devices) {
+               return 0;
+       }
+       for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
+               if (unittable[i] == unitnum + 1) {
+                       return 0;
+               }
+       }
+       di = &dev_info[unitnum];
+       unittable[unitnum] = unitnum + 1;
+       if (open_sg_device2(di, unitnum)) {
+               return 1;
+       }
+       unittable[unitnum] = 0;
+       return 0;
+}
+
+static void close_sg_device(int unitnum)
+{
+       struct dev_info_sg *di = unitisopen(unitnum);
+       if (!di) {
+               return;
+       }
+       close_sg_device2(di);
+       unittable[unitnum] = 0;
+}
+
+static struct device_info *info_sg_device(int unitnum, struct device_info *di, int quick, int session)
+{
+       struct dev_info_sg *disg = unitcheck(unitnum);
+       if (!disg) {
+               return NULL;
+       }
+       if (!quick) {
+               update_device_info(unitnum);
+       }
+       disg->di.open = disg->open;
+       memcpy(di, &disg->di, sizeof(struct device_info));
+       return di;
+}
+
+static int sg_exec_direct_common(struct dev_info_sg *di, int unitnum, struct amigascsi *as)
+{
+       uae_u8 cmd[16];
+       uae_u8 *scsi_datap = as->len ? as->data : NULL;
+       uae_u8 *scsi_datap_org = scsi_datap;
+       int datalen = as->len;
+       int cmdlen = as->cmd_len;
+       int parm = 0;
+
+       if (datalen > DEVICE_SCSI_BUFSIZE) {
+               datalen = DEVICE_SCSI_BUFSIZE;
+       }
+       if (cmdlen > (int)sizeof(cmd)) {
+               cmdlen = sizeof(cmd);
+       }
+       memcpy(cmd, as->cmd, cmdlen);
+
+       if (di->isatapi) {
+               scsi_atapi_fixup_pre(cmd, &cmdlen, &scsi_datap, &datalen, &parm);
+       }
+
+       if (datalen > 0 && scsi_datap) {
+               memcpy(di->scsibuf, scsi_datap, datalen);
+       }
+
+       const int direction = (as->flags & 1) ? SG_DXFER_FROM_DEV : SG_DXFER_TO_DEV;
+       uae_u8 status = 0;
+       int actual = 0;
+       int senselen = as->sense_len;
+       if (senselen > (int)sizeof(di->sense)) {
+               senselen = sizeof(di->sense);
+       }
+       if (as->flags & 4) {
+               senselen = std::min(senselen, 4);
+       }
+
+       uae_sem_wait(&scgp_sem);
+       int ret = sg_exec_fd(di->fd, cmd, cmdlen, datalen > 0 ? di->scsibuf : NULL, datalen,
+               direction, di->sense, senselen, &status, &actual, 80 * 60 * 1000);
+       uae_sem_post(&scgp_sem);
+
+       if (datalen > 0 && scsi_datap && direction == SG_DXFER_FROM_DEV) {
+               memcpy(scsi_datap, di->scsibuf, datalen);
+       }
+
+       as->status = status;
+       as->cmdactual = ret < 0 ? 0 : as->cmd_len;
+       as->sactual = 0;
+       if (status) {
+               for (int i = 0; i < senselen; i++) {
+                       as->sensedata[i] = di->sense[i];
+                       as->sactual++;
+               }
+               as->actual = 0;
+               if (scsi_datap != scsi_datap_org) {
+                       free(scsi_datap);
+               }
+               return IOERR_BadStatus;
+       }
+
+       if (ret < 0) {
+               as->actual = 0;
+               if (scsi_datap != scsi_datap_org) {
+                       free(scsi_datap);
+               }
+               return IOERR_NotSpecified;
+       }
+
+       as->len = actual;
+       if (di->isatapi) {
+               scsi_atapi_fixup_post(cmd, cmdlen, scsi_datap_org, scsi_datap, &as->len, parm);
+       }
+       as->actual = as->len;
+       for (int i = 0; i < as->sense_len; i++) {
+               as->sensedata[i] = 0;
+       }
+       if (scsi_datap != scsi_datap_org) {
+               free(scsi_datap);
+       }
+       return 0;
+}
+
+static int execsg_direct(int unitnum, struct amigascsi *as)
+{
+       struct dev_info_sg *di = unitisopen(unitnum);
+       if (!di) {
+               return -1;
+       }
+
+       if (as->cmd[0] == 0x03 && di->senselen > 0) {
+               int len = di->senselen;
+               if (len > as->len) {
+                       len = as->len;
+               }
+               memcpy(as->data, di->sense, len);
+               as->actual = len;
+               as->status = 0;
+               as->sactual = 0;
+               as->cmdactual = as->cmd_len;
+               di->senselen = 0;
+               return 0;
+       }
+
+       return sg_exec_direct_common(di, unitnum, as);
+}
+
+static uae_u8 *execsg_out(int unitnum, uae_u8 *data, int len)
+{
+       struct amigascsi as;
+       memset(&as, 0, sizeof(as));
+       as.data = data;
+       as.len = 0;
+       memcpy(as.cmd, data, len);
+       as.cmd_len = len;
+       as.flags = 0;
+       if (execsg_direct(unitnum, &as) != 0) {
+               return NULL;
+       }
+       return data;
+}
+
+static uae_u8 *execsg_in(int unitnum, uae_u8 *data, int len, int *outlen)
+{
+       struct dev_info_sg *di = unitisopen(unitnum);
+       if (!di) {
+               return NULL;
+       }
+       struct amigascsi as;
+       memset(&as, 0, sizeof(as));
+       as.data = di->scsibuf;
+       as.len = DEVICE_SCSI_BUFSIZE;
+       memcpy(as.cmd, data, len);
+       as.cmd_len = len;
+       as.flags = 1;
+       as.sense_len = sizeof(as.sensedata);
+       if (execsg_direct(unitnum, &as) != 0 || as.actual <= 0) {
+               return NULL;
+       }
+       if (outlen) {
+               *outlen = std::min(*outlen, (int)as.actual);
+       }
+       return di->scsibuf;
+}
+
+static int sg_isatapi(int unitnum)
+{
+       struct dev_info_sg *di = unitcheck(unitnum);
+       return di && di->isatapi;
+}
+
+static int sg_ismedia(int unitnum, int quick)
+{
+       struct dev_info_sg *di = unitisopen(unitnum);
+       if (!di) {
+               return -1;
+       }
+       if (quick) {
+               return di->di.media_inserted;
+       }
+       update_device_info(unitnum);
+       return di->di.media_inserted;
+}
+
+static int open_sg_bus(int flags)
+{
+       if (bus_open) {
+               write_log(_T("SG_IO open_bus() more than once!\n"));
+               return 1;
+       }
+       total_devices = 0;
+       memset(dev_info, 0, sizeof(dev_info));
+       memset(unittable, 0, sizeof(unittable));
+       for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
+               dev_info[i].fd = -1;
+       }
+
+       uae_sem_init(&scgp_sem, 0, 1);
+       const std::vector<std::string> paths = candidate_paths();
+       for (const std::string &path : paths) {
+               add_device(path.c_str());
+       }
+       bus_open = 1;
+       write_log(_T("SG_IO driver open, %d devices.\n"), total_devices);
+       return total_devices;
+}
+
+static void close_sg_bus(void)
+{
+       if (!bus_open) {
+               write_log(_T("SG_IO close_bus() when already closed!\n"));
+               return;
+       }
+       for (int i = 0; i < MAX_TOTAL_SCSI_DEVICES; i++) {
+               close_sg_device2(&dev_info[i]);
+               xfree(dev_info[i].scsibuf);
+               dev_info[i].scsibuf = NULL;
+               unittable[i] = 0;
+       }
+       total_devices = 0;
+       bus_open = 0;
+       uae_sem_destroy(&scgp_sem);
+       write_log(_T("SG_IO driver closed.\n"));
+}
+
+struct device_functions devicefunc_scsi_spti = {
+       _T("SG_IO"),
+       open_sg_bus, close_sg_bus, open_sg_device, close_sg_device, info_sg_device,
+       execsg_out, execsg_in, execsg_direct,
+       0, 0, 0, 0, 0,
+       0, 0, 0, 0,
+       sg_isatapi, sg_ismedia, 0
+};
+
+#endif /* WITH_SCSI_SPTI */
diff --git a/od-unix/cda_play.cpp b/od-unix/cda_play.cpp
new file mode 100644 (file)
index 0000000..da2c7ae
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+* UAE
+*
+* Unix audio player for CDA emulation
+*
+*/
+
+#define CDADS 0
+
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#include "options.h"
+#include "uae.h"
+#include "audio.h"
+#include "blkdev.h"
+#include "threaddep/thread.h"
+
+#include <sys/time.h>
+
+#include "gui.h"
+
+#include "cda_play.h"
+
+cda_audio::~cda_audio()
+{
+       for (int i = 0; i < 2; i++) {
+               xfree(buffers[i]);
+               buffers[i] = NULL;
+       }
+}
+
+cda_audio::cda_audio(int num_sectors, int sectorsize, int samplerate)
+{
+       (void)samplerate;
+
+       active = false;
+       playing = false;
+       volume[0] = volume[1] = 0;
+
+       bufsize = num_sectors * sectorsize;
+       this->sectorsize = sectorsize;
+       for (int i = 0; i < 2; i++) {
+               buffers[i] = xcalloc(uae_u8, num_sectors * ((bufsize + 4095) & ~4095));
+       }
+       this->num_sectors = num_sectors;
+}
+
+void cda_audio::setvolume(int left, int right)
+{
+       for (int j = 0; j < 2; j++) {
+               volume[j] = j == 0 ? left : right;
+               volume[j] = sound_cd_volume[j] * volume[j] / 32768;
+               if (volume[j])
+                       volume[j]++;
+               volume[j] = volume[j] * (100 - currprefs.sound_volume_master) / 100;
+               if (volume[j] >= 32768)
+                       volume[j] = 32768;
+       }
+}
+
+static uae_sem_t play_sem;
+static bool play_sem_initialized;
+
+static void ensure_play_sem(void)
+{
+       if (!play_sem_initialized) {
+               uae_sem_init(&play_sem, 0, 1);
+               play_sem_initialized = true;
+       }
+}
+
+static uae_s64 cdda_get_millis(void)
+{
+       struct timeval tv;
+
+       gettimeofday(&tv, NULL);
+       return tv.tv_sec * (uae_s64)1000 + tv.tv_usec / 1000;
+}
+
+static void sub_deinterleave(const uae_u8 *s, uae_u8 *d)
+{
+       for (int i = 0; i < 8 * 12; i++) {
+               int dmask = 0x80;
+               int smask = 1 << (7 - (i / 12));
+               (*d) = 0;
+               for (int j = 0; j < 8; j++) {
+                       (*d) |= (s[(i % 12) * 8 + j] & smask) ? dmask : 0;
+                       dmask >>= 1;
+               }
+               d++;
+       }
+}
+
+int ciw_cdda_setstate(struct cda_play *ciw, int state, int playpos)
+{
+       ciw->cdda_play_state = state;
+       if (ciw->cdda_statusfunc)
+               return ciw->cdda_statusfunc(ciw->cdda_play_state, playpos);
+       return 0;
+}
+
+static void ioctl_next_cd_audio_buffer_callback(int bufnum, void *param)
+{
+       struct cda_play *ciw = (struct cda_play*)param;
+
+       uae_sem_wait(&play_sem);
+       if (bufnum >= 0) {
+               ciw->cda_bufon[bufnum] = 0;
+               bufnum = 1 - bufnum;
+               if (ciw->cda_bufon[bufnum])
+                       audio_cda_new_buffer(&ciw->cas, (uae_s16*)ciw->cda->buffers[bufnum], CDDA_BUFFERS * 2352 / 4, bufnum, ioctl_next_cd_audio_buffer_callback, ciw);
+               else
+                       bufnum = -1;
+       }
+       if (bufnum < 0) {
+               audio_cda_new_buffer(&ciw->cas, NULL, 0, -1, NULL, ciw);
+       }
+       uae_sem_post(&play_sem);
+}
+
+static bool cdda_play2(struct cda_play *ciw, int *outpos)
+{
+       int cdda_pos = ciw->cdda_start;
+       int bufnum;
+       int buffered;
+       int i;
+       int oldplay;
+       int idleframes;
+       int muteframes;
+       int readblocksize = 2352 + 96;
+       bool restart = false;
+
+       ensure_play_sem();
+
+       while (ciw->cdda_play == 0)
+               sleep_millis(10);
+       oldplay = -1;
+
+       ciw->cda_bufon[0] = ciw->cda_bufon[1] = 0;
+       bufnum = 0;
+       buffered = 0;
+
+       memset(&ciw->cas, 0, sizeof(struct cd_audio_state));
+       ciw->cda = new cda_audio(CDDA_BUFFERS, 2352, 44100);
+
+       while (ciw->cdda_play > 0) {
+
+               while (ciw->cda_bufon[bufnum] && ciw->cdda_play > 0) {
+                       if (cd_audio_mode_changed) {
+                               restart = true;
+                               goto end;
+                       }
+                       sleep_millis(10);
+               }
+               if (ciw->cdda_play <= 0)
+                       goto end;
+               ciw->cda_bufon[bufnum] = 0;
+
+               if (oldplay != ciw->cdda_play) {
+                       idleframes = 0;
+                       muteframes = 0;
+                       bool seensub = false;
+                       uae_s64 tb1, tb2;
+                       tb1 = cdda_get_millis();
+                       cdda_pos = ciw->cdda_start;
+                       ciw->cd_last_pos = cdda_pos;
+                       oldplay = ciw->cdda_play;
+                       write_log(_T("CDDA: playing from %d to %d\n"), ciw->cdda_start, ciw->cdda_end);
+                       ciw->subcodevalid = false;
+                       idleframes = ciw->cdda_delay_frames;
+                       while (ciw->cdda_paused && ciw->cdda_play > 0) {
+                               sleep_millis(10);
+                               idleframes = -1;
+                       }
+                       if (isaudiotrack(&ciw->di->toc, cdda_pos))
+                               ciw->read_block(ciw, ciw->unitnum, ciw->cda->buffers[bufnum], cdda_pos, CDDA_BUFFERS, readblocksize);
+                       if (!isaudiotrack(&ciw->di->toc, cdda_pos - 150))
+                               muteframes = 75;
+
+                       if (ciw->cdda_scan == 0) {
+                               bool seenindex = false;
+                               for (int sector = cdda_pos - 200; sector < cdda_pos; sector++) {
+                                       uae_u8 *dst = ciw->cda->buffers[bufnum];
+                                       if (sector >= 0 && isaudiotrack(&ciw->di->toc, sector) && ciw->read_block(ciw, ciw->unitnum, dst, sector, 1, readblocksize) > 0) {
+                                               uae_u8 subbuf[SUB_CHANNEL_SIZE];
+                                               sub_deinterleave(dst + 2352, subbuf);
+                                               if (seenindex) {
+                                                       for (int i = 2 * SUB_ENTRY_SIZE; i < SUB_CHANNEL_SIZE; i++) {
+                                                               if (subbuf[i]) {
+                                                                       int diff = cdda_pos - sector + 2;
+                                                                       write_log(_T("-> CD+G start pos fudge -> %d (%d)\n"), sector, -diff);
+                                                                       idleframes -= diff;
+                                                                       cdda_pos = sector;
+                                                                       seensub = true;
+                                                                       break;
+                                                               }
+                                                       }
+                                               } else if (subbuf[0] == 0xff) {
+                                                       seenindex = true;
+                                               }
+                                       }
+                               }
+                       }
+                       cdda_pos -= idleframes;
+
+                       if (*outpos < 0) {
+                               tb2 = cdda_get_millis();
+                               int diff = (int)(tb2 - tb1);
+                               diff -= ciw->cdda_delay;
+                               if (idleframes >= 0 && diff < 0 && ciw->cdda_play > 0)
+                                       sleep_millis(-diff);
+                               if (diff > 0 && !seensub) {
+                                       int ch = diff / 7 + 25;
+                                       if (ch > idleframes)
+                                               ch = idleframes;
+                                       idleframes -= ch;
+                                       cdda_pos += ch;
+                               }
+                               ciw_cdda_setstate(ciw, AUDIO_STATUS_IN_PROGRESS, cdda_pos);
+                       }
+               }
+
+               if ((cdda_pos < ciw->cdda_end || ciw->cdda_end == 0xffffffff) && !ciw->cdda_paused && ciw->cdda_play) {
+
+                       if (idleframes <= 0 && cdda_pos >= ciw->cdda_start && !isaudiotrack(&ciw->di->toc, cdda_pos)) {
+                               ciw_cdda_setstate(ciw, AUDIO_STATUS_PLAY_ERROR, -1);
+                               write_log(_T("IOCTL: attempted to play data track %d\n"), cdda_pos);
+                               goto end;
+                       }
+
+                       gui_flicker_led(LED_CD, ciw->unitnum - 1, LED_CD_AUDIO);
+
+                       uae_sem_wait(&ciw->sub_sem);
+
+                       ciw->subcodevalid = false;
+                       memset(ciw->subcode, 0, sizeof ciw->subcode);
+                       memset(ciw->cda->buffers[bufnum], 0, CDDA_BUFFERS * readblocksize);
+
+                       if (cdda_pos >= 0) {
+
+                               ciw_cdda_setstate(ciw, AUDIO_STATUS_IN_PROGRESS, cdda_pos);
+
+                               int r = ciw->read_block(ciw, ciw->unitnum, ciw->cda->buffers[bufnum], cdda_pos, CDDA_BUFFERS, readblocksize);
+                               if (r < 0) {
+                                       ciw_cdda_setstate(ciw, AUDIO_STATUS_PLAY_ERROR, -1);
+                                       uae_sem_post(&ciw->sub_sem);
+                                       goto end;
+                               }
+                               if (r > 0) {
+                                       for (i = 0; i < CDDA_BUFFERS; i++) {
+                                               memcpy(ciw->subcode + i * SUB_CHANNEL_SIZE, ciw->cda->buffers[bufnum] + readblocksize * i + 2352, SUB_CHANNEL_SIZE);
+                                       }
+                                       for (i = 1; i < CDDA_BUFFERS; i++) {
+                                               memmove(ciw->cda->buffers[bufnum] + 2352 * i, ciw->cda->buffers[bufnum] + readblocksize * i, 2352);
+                                       }
+                                       ciw->subcodevalid = true;
+                               }
+                       }
+
+                       for (i = 0; i < CDDA_BUFFERS; i++) {
+                               if (muteframes > 0) {
+                                       memset(ciw->cda->buffers[bufnum] + 2352 * i, 0, 2352);
+                                       muteframes--;
+                               }
+                               if (idleframes > 0) {
+                                       idleframes--;
+                                       memset(ciw->cda->buffers[bufnum] + 2352 * i, 0, 2352);
+                                       memset(ciw->subcode + i * SUB_CHANNEL_SIZE, 0, SUB_CHANNEL_SIZE);
+                               } else if (cdda_pos < ciw->cdda_start && ciw->cdda_scan == 0) {
+                                       memset(ciw->cda->buffers[bufnum] + 2352 * i, 0, 2352);
+                               }
+                       }
+                       if (idleframes > 0)
+                               ciw->subcodevalid = false;
+
+                       if (ciw->cdda_subfunc)
+                               ciw->cdda_subfunc(ciw->subcode, CDDA_BUFFERS);
+
+                       uae_sem_post(&ciw->sub_sem);
+
+                       if (ciw->subcodevalid) {
+                               uae_sem_wait(&ciw->sub_sem2);
+                               memcpy(ciw->subcodebuf, ciw->subcode + (CDDA_BUFFERS - 1) * SUB_CHANNEL_SIZE, SUB_CHANNEL_SIZE);
+                               uae_sem_post(&ciw->sub_sem2);
+                       }
+
+                       if (ciw->cda_bufon[0] == 0 && ciw->cda_bufon[1] == 0) {
+                               ciw->cda_bufon[bufnum] = 1;
+                               ioctl_next_cd_audio_buffer_callback(1 - bufnum, ciw);
+                       }
+                       audio_cda_volume(&ciw->cas, ciw->cdda_volume[0], ciw->cdda_volume[1]);
+                       ciw->cda_bufon[bufnum] = 1;
+
+                       if (ciw->cdda_scan) {
+                               cdda_pos += ciw->cdda_scan * CDDA_BUFFERS;
+                               if (cdda_pos < 0)
+                                       cdda_pos = 0;
+                       } else {
+                               if (cdda_pos < 0 && cdda_pos + CDDA_BUFFERS >= 0)
+                                       cdda_pos = 0;
+                               else
+                                       cdda_pos += CDDA_BUFFERS;
+                       }
+
+                       if (idleframes <= 0) {
+                               if (cdda_pos - CDDA_BUFFERS < ciw->cdda_end && cdda_pos >= ciw->cdda_end) {
+                                       cdda_pos = ciw->cdda_end;
+                                       ciw_cdda_setstate(ciw, AUDIO_STATUS_PLAY_COMPLETE, cdda_pos);
+                                       ciw->cdda_play_finished = 1;
+                                       ciw->cdda_play = -1;
+                               }
+                               ciw->cd_last_pos = cdda_pos;
+                       }
+               }
+
+               if (ciw->cda_bufon[0] == 0 && ciw->cda_bufon[1] == 0) {
+                       while (ciw->cdda_paused && ciw->cdda_play == oldplay)
+                               sleep_millis(10);
+               }
+
+               if (cd_audio_mode_changed) {
+                       restart = true;
+                       goto end;
+               }
+
+               bufnum = 1 - bufnum;
+               buffered++;
+       }
+
+end:
+       (void)buffered;
+
+       *outpos = cdda_pos;
+       ioctl_next_cd_audio_buffer_callback(-1, ciw);
+       if (restart)
+               audio_cda_new_buffer(&ciw->cas, NULL, -1, -1, NULL, ciw);
+
+       ciw->subcodevalid = false;
+       cd_audio_mode_changed = false;
+       delete ciw->cda;
+       ciw->cda = NULL;
+
+       write_log(_T("CDDA: thread killed\n"));
+       return restart;
+}
+
+void ciw_cdda_play(void *v)
+{
+       struct cda_play *ciw = (struct cda_play*)v;
+       int outpos = -1;
+
+       cd_audio_mode_changed = false;
+       for (;;) {
+               if (!cdda_play2(ciw, &outpos)) {
+                       break;
+               }
+               ciw->cdda_start = outpos;
+               if (ciw->cdda_start + 150 >= ciw->cdda_end) {
+                       if (ciw->cdda_play >= 0)
+                               ciw_cdda_setstate(ciw, AUDIO_STATUS_PLAY_COMPLETE, ciw->cdda_end + 1);
+                       ciw->cdda_play = -1;
+                       break;
+               }
+               ciw->cdda_play = 1;
+       }
+       ciw->cdda_play = 0;
+}
+
+void ciw_cdda_stop(struct cda_play *ciw)
+{
+       if (ciw->cdda_play != 0) {
+               ciw->cdda_play = -1;
+               while (ciw->cdda_play) {
+                       sleep_millis(10);
+               }
+       }
+       ciw->cdda_play_finished = 0;
+       ciw->cdda_paused = 0;
+       ciw->subcodevalid = 0;
+}
diff --git a/od-unix/cda_play.h b/od-unix/cda_play.h
new file mode 100644 (file)
index 0000000..6299f91
--- /dev/null
@@ -0,0 +1,63 @@
+#ifndef WINUAE_OD_UNIX_CDA_PLAY_H
+#define WINUAE_OD_UNIX_CDA_PLAY_H
+
+#include "sysconfig.h"
+#include "sysdeps.h"
+#include "audio.h"
+#include "blkdev.h"
+
+#define CDDA_BUFFERS 14
+
+extern volatile bool cd_audio_mode_changed;
+
+class cda_audio
+{
+private:
+    int bufsize;
+    int num_sectors;
+    int sectorsize;
+    int volume[2];
+    bool playing;
+    bool active;
+
+public:
+    uae_u8 *buffers[2];
+
+    cda_audio(int num_sectors, int sectorsize, int samplerate);
+    ~cda_audio();
+    void setvolume(int left, int right);
+};
+
+struct cda_play;
+typedef int (*cda_play_read_block)(struct cda_play *, int, uae_u8 *, int, int, int);
+
+struct cda_play
+{
+    int unitnum;
+    int cdda_volume[2];
+    int cd_last_pos;
+    int cdda_play;
+    int cdda_play_finished;
+    int cdda_scan;
+    int cdda_paused;
+    int cdda_start, cdda_end;
+    play_subchannel_callback cdda_subfunc;
+    play_status_callback cdda_statusfunc;
+    int cdda_play_state;
+    int cdda_delay, cdda_delay_frames;
+    cda_audio *cda;
+    volatile int cda_bufon[2];
+    struct cd_audio_state cas;
+    bool subcodevalid;
+    uae_sem_t sub_sem, sub_sem2;
+    uae_u8 subcode[SUB_CHANNEL_SIZE * CDDA_BUFFERS];
+    uae_u8 subcodebuf[SUB_CHANNEL_SIZE];
+    struct device_info *di;
+    cda_play_read_block read_block;
+};
+
+void ciw_cdda_play(void *ciw);
+void ciw_cdda_stop(struct cda_play *ciw);
+int ciw_cdda_setstate(struct cda_play *ciw, int state, int playpos);
+
+#endif /* WINUAE_OD_UNIX_CDA_PLAY_H */
diff --git a/od-unix/chd_unix_time.cpp b/od-unix/chd_unix_time.cpp
new file mode 100644 (file)
index 0000000..48b386d
--- /dev/null
@@ -0,0 +1,30 @@
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#include "archivers/chd/chdtypes.h"
+#include "archivers/chd/osdcore.h"
+
+#include <chrono>
+#include <thread>
+
+static const osd_ticks_t unix_chd_ticks_per_second = 1000000000ULL;
+
+osd_ticks_t osd_ticks(void)
+{
+    using clock = std::chrono::steady_clock;
+    return (osd_ticks_t)std::chrono::duration_cast<std::chrono::nanoseconds>(
+        clock::now().time_since_epoch()).count();
+}
+
+osd_ticks_t osd_ticks_per_second(void)
+{
+    return unix_chd_ticks_per_second;
+}
+
+void osd_sleep(osd_ticks_t duration)
+{
+    if (duration == 0) {
+        return;
+    }
+    std::this_thread::sleep_for(std::chrono::nanoseconds(duration));
+}
diff --git a/od-unix/chd_unix_work.cpp b/od-unix/chd_unix_work.cpp
new file mode 100644 (file)
index 0000000..6c949c9
--- /dev/null
@@ -0,0 +1,93 @@
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#include "archivers/chd/chdtypes.h"
+#include "archivers/chd/osdcore.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+struct osd_work_queue
+{
+    int flags;
+    int items;
+};
+
+struct osd_work_item
+{
+    osd_work_queue *queue;
+    osd_work_callback callback;
+    void *param;
+    void *result;
+    int done;
+};
+
+osd_work_queue *osd_work_queue_alloc(int flags)
+{
+    osd_work_queue *queue = (osd_work_queue*)calloc(1, sizeof(*queue));
+    if (queue) {
+        queue->flags = flags;
+    }
+    return queue;
+}
+
+int osd_work_queue_items(osd_work_queue *queue)
+{
+    return queue ? queue->items : 0;
+}
+
+int osd_work_queue_wait(osd_work_queue *, osd_ticks_t)
+{
+    return TRUE;
+}
+
+void osd_work_queue_free(osd_work_queue *queue)
+{
+    free(queue);
+}
+
+osd_work_item *osd_work_item_queue_multiple(osd_work_queue *queue, osd_work_callback callback, INT32 numitems, void *parambase, INT32 paramstep, UINT32 flags)
+{
+    osd_work_item *last = NULL;
+    if (!queue || !callback || numitems <= 0) {
+        return NULL;
+    }
+
+    UINT8 *param = (UINT8*)parambase;
+    for (INT32 i = 0; i < numitems; i++) {
+        osd_work_item *item = (osd_work_item*)calloc(1, sizeof(*item));
+        if (!item) {
+            return last;
+        }
+        item->queue = queue;
+        item->callback = callback;
+        item->param = param;
+        queue->items++;
+        item->result = callback(param, 0);
+        item->done = TRUE;
+        queue->items--;
+
+        if (flags & WORK_ITEM_FLAG_AUTO_RELEASE) {
+            free(item);
+        } else {
+            last = item;
+        }
+        param += paramstep;
+    }
+    return (flags & WORK_ITEM_FLAG_AUTO_RELEASE) ? NULL : last;
+}
+
+int osd_work_item_wait(osd_work_item *item, osd_ticks_t)
+{
+    return !item || item->done;
+}
+
+void *osd_work_item_result(osd_work_item *item)
+{
+    return item ? item->result : NULL;
+}
+
+void osd_work_item_release(osd_work_item *item)
+{
+    free(item);
+}
diff --git a/od-unix/mp3decoder.h b/od-unix/mp3decoder.h
new file mode 100644 (file)
index 0000000..e04a86c
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef WINUAE_OD_UNIX_MP3DECODER_H
+#define WINUAE_OD_UNIX_MP3DECODER_H
+
+#include "uae/types.h"
+
+struct zfile;
+
+class mp3decoder
+{
+public:
+    mp3decoder(struct zfile*) {}
+    mp3decoder() {}
+    ~mp3decoder() {}
+    uae_u8 *get(struct zfile*, uae_u8*, int) { return nullptr; }
+    uae_u32 getsize(struct zfile*) { return 0; }
+};
+
+#endif /* WINUAE_OD_UNIX_MP3DECODER_H */