/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * Mitsumi CD-ROM emulation for the ISA bus. * * Authors: Miran Grca, * Jasmine Iwanek, * * Copyright 2022 Miran Grca. * Copyright 2024-2025 Jasmine Iwanek. */ #include #include #include #include #include #include #include #define HAVE_STDARG_H #include <86box/86box.h> #include <86box/device.h> #include <86box/io.h> #include <86box/pic.h> #include <86box/dma.h> #include <86box/cdrom.h> #include <86box/cdrom_interface.h> #include <86box/cdrom_mitsumi.h> #include <86box/plat.h> #include <86box/sound.h> #define RAW_SECTOR_SIZE 2352 #define COOKED_SECTOR_SIZE 2048 enum { STAT_CMD_CHECK = 0x01, STAT_PLAY_CDDA = 0x02, STAT_ERROR = 0x04, STAT_DISK_CDDA = 0x08, STAT_SPIN = 0x10, STAT_CHANGE = 0x20, STAT_READY = 0x40, STAT_OPEN = 0x80 }; enum { CMD_GET_INFO = 0x10, CMD_GET_Q = 0x20, CMD_GET_STAT = 0x40, CMD_SET_MODE = 0x50, CMD_SOFT_RESET = 0x60, CMD_STOPCDDA = 0x70, CMD_CONFIG = 0x90, CMD_SET_VOL = 0xae, CMD_READ1X = 0xc0, CMD_READ2X = 0xc1, CMD_GET_VER = 0xdc, CMD_STOP = 0xf0, CMD_EJECT = 0xf6, CMD_LOCK = 0xfe }; enum { MODE_MUTE = 0x01, MODE_GET_TOC = 0x04, MODE_STOP = 0x08, MODE_ECC = 0x20, MODE_DATA = 0x40 }; enum { DRV_MODE_STOP, DRV_MODE_READ, DRV_MODE_CDDA }; enum { FLAG_NODATA = 2, FLAG_NOSTAT = 4, FLAG_UNK = 8, //?? FLAG_OPEN = 16 }; enum { IRQ_DATAREADY = 1, IRQ_DATACOMP = 2, IRQ_ERROR = 4 }; typedef struct mcd_t { int dma; int irq; int change; int data; uint8_t stat; uint8_t buf[RAW_SECTOR_SIZE]; int buf_count; int buf_idx; uint8_t cmdbuf[16]; int cmdbuf_count; int cmdrd_count; int cmdbuf_idx; uint8_t mode; uint8_t cmd; uint8_t conf; uint8_t enable_irq; uint8_t enable_dma; uint16_t dmalen; uint32_t readmsf; uint32_t readcount; int locked; int drvmode; int cur_toc_track; int pos; int newstat; cdrom_t *cdrom_dev; } mcd_t; #define CD_BCD(x) (((x) % 10) | (((x) / 10) << 4)) #define CD_DCB(x) ((((x) &0xf0) >> 4) * 10 + ((x) &0x0f)) #ifdef ENABLE_MITSUMI_CDROM_LOG int mitsumi_cdrom_do_log = ENABLE_MITSUMI_CDROM_LOG; void mitsumi_cdrom_log(const char *fmt, ...) { va_list ap; if (mitsumi_cdrom_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else # define mitsumi_cdrom_log(fmt, ...) #endif static int mitsumi_cdrom_is_ready(const mcd_t *dev) { return (dev->cdrom_dev->image_path[0] != 0x00); } static void mitsumi_cdrom_reset(mcd_t *dev) { dev->stat = mitsumi_cdrom_is_ready(dev) ? (STAT_READY | STAT_CHANGE) : 0; dev->cmdrd_count = 0; dev->cmdbuf_count = 0; dev->buf_count = 0; dev->cur_toc_track = 0; dev->enable_dma = 0; dev->enable_irq = 0; dev->conf = 0; dev->dmalen = COOKED_SECTOR_SIZE; dev->locked = 0; dev->change = 1; dev->newstat = 1; dev->data = 0; } static int mitsumi_cdrom_read_sector(mcd_t *dev, int first) { uint8_t status; int ret = 0; if (dev->drvmode == DRV_MODE_CDDA) { status = cdrom_mitsumi_audio_play(dev->cdrom_dev, dev->readmsf, dev->readcount); if (status == 1) return status; else dev->drvmode = DRV_MODE_READ; } if ((dev->enable_irq & IRQ_DATACOMP) && !first) { picint(1 << dev->irq); } if (!dev->readcount) { dev->data = 0; return 0; } cdrom_stop(dev->cdrom_dev); ret = cdrom_readsector_raw(dev->cdrom_dev, dev->buf, dev->cdrom_dev->seek_pos, 0, 2, 0x10, (int *) &dev->readcount, 0); if (ret <= 0) return 0; if (dev->mode & 0x40) { dev->buf[12] = CD_BCD((dev->readmsf >> 16) & 0xff); dev->buf[13] = CD_BCD((dev->readmsf >> 8) & 0xff); } dev->readmsf = cdrom_lba_to_msf_accurate(dev->cdrom_dev->seek_pos + 1); dev->buf_count = dev->dmalen + 1; dev->buf_idx = 0; dev->data = 1; if (dev->enable_dma) { while (dev->pos < dev->readcount) { dma_channel_write(dev->dma, dev->buf[dev->pos]); dev->pos++; } dev->pos = 0; } dev->readcount--; if ((dev->enable_irq & IRQ_DATAREADY) && first) picint(1 << dev->irq); return 1; } static uint8_t mitsumi_cdrom_in(uint16_t port, void *priv) { mcd_t *dev = (mcd_t *) priv; uint8_t ret = 0xff; pclog("Mitsumi CD-ROM IN=%03x\n", port); switch (port & 1) { case 0: if (dev->cmdbuf_count) { dev->cmdbuf_count--; return dev->cmdbuf[dev->cmdbuf_idx++]; } else if (dev->buf_count) { ret = (dev->buf_idx < RAW_SECTOR_SIZE) ? dev->buf[dev->buf_idx] : 0; dev->buf_idx++; dev->buf_count--; if (!dev->buf_count) mitsumi_cdrom_read_sector(dev, 0); pclog("Read port 0: ret = %02x\n", ret); return ret; } pclog("Read port 0: stat = %02x\n", dev->stat); return dev->stat; case 1: ret = 0; picintc(1 << dev->irq); if (!dev->buf_count || !dev->data || dev->enable_dma) ret |= FLAG_NODATA; if (!dev->cmdbuf_count || !dev->newstat) ret |= FLAG_NOSTAT; pclog("Read port 1: ret = %02x\n", ret | FLAG_UNK); return ret | FLAG_UNK; case 2: break; default: break; } return ret; } static void mitsumi_cdrom_out(uint16_t port, uint8_t val, void *priv) { mcd_t *dev = (mcd_t *) priv; pclog("Mitsumi CD-ROM OUT=%03x, val=%02x\n", port, val); switch (port & 1) { case 0: if (dev->cmdrd_count) { dev->cmdrd_count--; switch (dev->cmd) { case CMD_SET_MODE: dev->mode = val; dev->cmdbuf[1] = 0; dev->cmdbuf_count = 2; break; case CMD_LOCK: dev->locked = val & 1; dev->cmdbuf[1] = 0; dev->cmdbuf[2] = 0; dev->cmdbuf_count = 3; break; case CMD_CONFIG: switch (dev->cmdrd_count) { case 0: switch (dev->conf) { case 0x01: dev->dmalen |= val; break; case 0x02: dev->enable_dma = val; break; case 0x10: dev->enable_irq = val; break; default: break; } dev->cmdbuf[1] = 0; dev->cmdbuf_count = 2; dev->conf = 0; break; case 1: if (dev->conf == 1) { dev->dmalen = val << 8; break; } dev->conf = val; if (dev->conf == 1) dev->cmdrd_count++; break; default: break; } break; case CMD_READ1X: case CMD_READ2X: switch (dev->cmdrd_count) { case 0: dev->readcount |= val; mitsumi_cdrom_read_sector(dev, 1); dev->cmdbuf_count = 1; dev->cmdbuf[0] = STAT_SPIN | STAT_READY; break; case 1: dev->readcount |= (val << 8); break; case 2: dev->readcount = (val << 16); break; case 5: dev->readmsf = 0; fallthrough; case 4: case 3: dev->readmsf |= CD_DCB(val) << ((dev->cmdrd_count - 3) << 3); break; default: break; } break; default: break; } if (!dev->cmdrd_count) dev->stat = mitsumi_cdrom_is_ready(dev) ? (STAT_READY | (dev->change ? STAT_CHANGE : 0)) : 0; return; } dev->cmd = val; dev->cmdbuf_idx = 0; dev->cmdrd_count = 0; dev->cmdbuf_count = 1; dev->cmdbuf[0] = mitsumi_cdrom_is_ready(dev) ? (STAT_READY | (dev->change ? STAT_CHANGE : 0)) : 0; dev->data = 0; switch (val) { case CMD_GET_INFO: if (mitsumi_cdrom_is_ready(dev)) { cdrom_get_track_buffer(dev->cdrom_dev, &(dev->cmdbuf[1])); dev->cmdbuf_count = 10; dev->readcount = 0; } else { dev->cmdbuf_count = 1; dev->cmdbuf[0] = STAT_CMD_CHECK; } break; case CMD_GET_Q: if (mitsumi_cdrom_is_ready(dev)) { cdrom_get_q(cdrom, &(dev->cmdbuf[1]), &dev->cur_toc_track, dev->mode & MODE_GET_TOC); dev->cmdbuf_count = 11; dev->readcount = 0; } else { dev->cmdbuf_count = 1; dev->cmdbuf[0] = STAT_CMD_CHECK; } break; case CMD_GET_STAT: dev->change = 0; break; case CMD_SET_MODE: dev->cmdrd_count = 1; break; case CMD_STOPCDDA: case CMD_STOP: cdrom_stop(dev->cdrom_dev); dev->drvmode = DRV_MODE_STOP; dev->cur_toc_track = 0; break; case CMD_CONFIG: dev->cmdrd_count = 2; break; case CMD_READ1X: case CMD_READ2X: if (mitsumi_cdrom_is_ready(dev)) { dev->readcount = 0; dev->drvmode = (val == CMD_READ1X) ? DRV_MODE_CDDA : DRV_MODE_READ; dev->cmdrd_count = 6; } else { dev->cmdbuf_count = 1; dev->cmdbuf[0] = STAT_CMD_CHECK; } break; case CMD_GET_VER: dev->cmdbuf[0] = 1; dev->cmdbuf[1] = 'D'; dev->cmdbuf[2] = 0; dev->cmdbuf_count = 3; break; case CMD_EJECT: cdrom_stop(dev->cdrom_dev); cdrom_eject(0); dev->readcount = 0; break; case CMD_LOCK: dev->cmdrd_count = 1; break; case CMD_SOFT_RESET: pclog("Soft Reset\n"); mitsumi_cdrom_reset(dev); break; default: dev->cmdbuf[0] = dev->stat | STAT_CMD_CHECK; break; } break; case 1: mitsumi_cdrom_reset(dev); break; case 2: break; default: break; } } static void * mitsumi_cdrom_init(UNUSED(const device_t *info)) { mcd_t *dev = calloc(1, sizeof(mcd_t)); for (uint8_t i = 0; i < CDROM_NUM; i++) { if (cdrom[i].bus_type == CDROM_BUS_MITSUMI) { dev->cdrom_dev = &cdrom[i]; break; } } if (!dev->cdrom_dev) return NULL; dev->cdrom_dev->priv = &dev; uint16_t base = device_get_config_hex16("base"); dev->irq = device_get_config_int("irq"); dev->dma = device_get_config_int("dma"); io_sethandler(base, 3, mitsumi_cdrom_in, NULL, NULL, mitsumi_cdrom_out, NULL, NULL, dev); mitsumi_cdrom_reset(dev); return dev; } static void mitsumi_cdrom_close(void *priv) { mcd_t *dev = (mcd_t *) priv; if (dev) { free(dev); dev = NULL; } } static const device_config_t mitsumi_config[] = { // clang-format off { .name = "base", .description = "Address", .type = CONFIG_HEX16, .default_string = NULL, .default_int = 0x310, .file_filter = NULL, .spinner = { 0 }, .selection = { { .description = "300H", .value = 0x300 }, { .description = "310H", .value = 0x310 }, { .description = "320H", .value = 0x320 }, { .description = "340H", .value = 0x340 }, { .description = "350H", .value = 0x350 }, { NULL } }, .bios = { { 0 } } }, { .name = "irq", .description = "IRQ", .type = CONFIG_SELECTION, .default_string = NULL, .default_int = 5, .file_filter = NULL, .spinner = { 0 }, .selection = { { .description = "IRQ 3", .value = 3 }, { .description = "IRQ 5", .value = 5 }, { .description = "IRQ 9", .value = 9 }, { .description = "IRQ 10", .value = 10 }, { .description = "IRQ 11", .value = 11 }, { .description = "" } }, .bios = { { 0 } } }, { .name = "dma", .description = "DMA", .type = CONFIG_SELECTION, .default_string = NULL, .default_int = 5, .file_filter = NULL, .spinner = { 0 }, .selection = { { .description = "DMA 5", .value = 5 }, { .description = "DMA 6", .value = 6 }, { .description = "DMA 7", .value = 7 }, { .description = "" } }, .bios = { { 0 } } }, { .name = "", .description = "", .type = CONFIG_END } // clang-format off }; const device_t mitsumi_cdrom_device = { .name = "Mitsumi CD-ROM interface", .internal_name = "mcd", .flags = DEVICE_ISA16, .local = 0, .init = mitsumi_cdrom_init, .close = mitsumi_cdrom_close, .reset = NULL, .available = NULL, .speed_changed = NULL, .force_redraw = NULL, .config = mitsumi_config };