/* * 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, * * Copyright 2022 Miran Grca. */ #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 MCD_DEFAULT_IOPORT 0x310 #define MCD_DEFAULT_IRQ 5 #define MCD_DEFAULT_DMA 5 #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; } 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 cdrom_t *dev) { return (dev->image_path[0] != 0x00); } static void mitsumi_cdrom_reset(mcd_t *dev) { cdrom_t *cdrom = calloc(1, sizeof(cdrom_t)); dev->stat = mitsumi_cdrom_is_ready(cdrom) ? (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) { cdrom_t *cdrom = calloc(1, sizeof(cdrom_t)); uint8_t status; int ret = 0; if (dev->drvmode == DRV_MODE_CDDA) { status = cdrom_mitsumi_audio_play(cdrom, 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(cdrom); ret = cdrom_readsector_raw(cdrom, dev->buf, cdrom->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(cdrom->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; cdrom_t *cdrom = calloc(1, sizeof(cdrom_t)); 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(cdrom) ? (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(cdrom) ? (STAT_READY | (dev->change ? STAT_CHANGE : 0)) : 0; dev->data = 0; switch (val) { case CMD_GET_INFO: if (mitsumi_cdrom_is_ready(cdrom)) { cdrom_get_track_buffer(cdrom, &(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(cdrom)) { 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(cdrom); 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(cdrom)) { 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(cdrom); 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)); dev->irq = MCD_DEFAULT_IRQ; dev->dma = MCD_DEFAULT_DMA; io_sethandler(MCD_DEFAULT_IOPORT, 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; } } 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 = NULL };