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.
--- /dev/null
+#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 */
--- /dev/null
+/*
+* 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 */
--- /dev/null
+/*
+ * 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, ®istry_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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+* 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;
+}
--- /dev/null
+#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 */
--- /dev/null
+#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));
+}
--- /dev/null
+#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);
+}
--- /dev/null
+#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 */