From 31f833d4daa72b13948d6d73e01204a393fd8d1a Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 4 Jun 2026 07:58:40 -0700 Subject: [PATCH] od-unix: add storage and media backends 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 | 100 ++ od-unix/blkdev_unix_ioctl.cpp | 1670 ++++++++++++++++++++++++++++ od-unix/blkdev_unix_scsi_macos.cpp | 602 ++++++++++ od-unix/blkdev_unix_sg.cpp | 684 ++++++++++++ od-unix/cda_play.cpp | 381 +++++++ od-unix/cda_play.h | 63 ++ od-unix/chd_unix_time.cpp | 30 + od-unix/chd_unix_work.cpp | 93 ++ od-unix/mp3decoder.h | 18 + 9 files changed, 3641 insertions(+) create mode 100644 od-unix/FLAC/stream_decoder.h create mode 100644 od-unix/blkdev_unix_ioctl.cpp create mode 100644 od-unix/blkdev_unix_scsi_macos.cpp create mode 100644 od-unix/blkdev_unix_sg.cpp create mode 100644 od-unix/cda_play.cpp create mode 100644 od-unix/cda_play.h create mode 100644 od-unix/chd_unix_time.cpp create mode 100644 od-unix/chd_unix_work.cpp create mode 100644 od-unix/mp3decoder.h diff --git a/od-unix/FLAC/stream_decoder.h b/od-unix/FLAC/stream_decoder.h new file mode 100644 index 00000000..b1bc0b99 --- /dev/null +++ b/od-unix/FLAC/stream_decoder.h @@ -0,0 +1,100 @@ +#ifndef WINUAE_OD_UNIX_FLAC_STREAM_DECODER_H +#define WINUAE_OD_UNIX_FLAC_STREAM_DECODER_H + +#include +#include + +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 index 00000000..9a8ee51e --- /dev/null +++ b/od-unix/blkdev_unix_ioctl.cpp @@ -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 +#include +#include +#include + +#ifdef UAE_HOST_DARWIN +// macOS — IOKit/DiskArbitration CD-ROM access (not available on iOS) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef ENOMEDIUM +#define ENOMEDIUM ENXIO +#endif + +#elif defined(__HAIKU__) +#include +#elif defined(__FreeBSD__) +// FreeBSD +#include +#include +#include + +#else +// Linux +#include +#include +#include +#include + +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "cda_play.h" +#ifdef RETROPLATFORM +#include "rp.h" +#endif + +#define IOCTL_DATA_BUFFER 8192 + +static std::vector get_cd_drives() +{ + std::vector 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 index 00000000..9bcec9f4 --- /dev/null +++ b/od-unix/blkdev_unix_scsi_macos.cpp @@ -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 +#include +#include +#include + +#include +#include +#include + +#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(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 */ diff --git a/od-unix/blkdev_unix_sg.cpp b/od-unix/blkdev_unix_sg.cpp new file mode 100644 index 00000000..ff030517 --- /dev/null +++ b/od-unix/blkdev_unix_sg.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 *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 candidate_paths() +{ + std::vector 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 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 index 00000000..da2c7ae0 --- /dev/null +++ b/od-unix/cda_play.cpp @@ -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 + +#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 index 00000000..6299f91e --- /dev/null +++ b/od-unix/cda_play.h @@ -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 index 00000000..48b386d6 --- /dev/null +++ b/od-unix/chd_unix_time.cpp @@ -0,0 +1,30 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#include "archivers/chd/chdtypes.h" +#include "archivers/chd/osdcore.h" + +#include +#include + +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( + 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 index 00000000..6c949c97 --- /dev/null +++ b/od-unix/chd_unix_work.cpp @@ -0,0 +1,93 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#include "archivers/chd/chdtypes.h" +#include "archivers/chd/osdcore.h" + +#include +#include + +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 index 00000000..e04a86c1 --- /dev/null +++ b/od-unix/mp3decoder.h @@ -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 */ -- 2.47.3