diff --git a/hash/altos586.xml b/hash/altos586.xml new file mode 100644 index 00000000000..45547fbe126 --- /dev/null +++ b/hash/altos586.xml @@ -0,0 +1,19 @@ + + + + + + + Altos Diagnostic Executive + 1983 + Altos Computer Systems + + + + + + + + diff --git a/src/devices/cpu/i86/i186.h b/src/devices/cpu/i86/i186.h index a21aeac7c23..9683bf8336b 100644 --- a/src/devices/cpu/i86/i186.h +++ b/src/devices/cpu/i86/i186.h @@ -78,7 +78,7 @@ protected: virtual uint8_t read_port_byte(uint16_t port) override; virtual uint16_t read_port_word(uint16_t port) override; virtual void write_port_byte(uint16_t port, uint8_t data) override; - void write_port_byte_al(uint16_t port); + virtual void write_port_byte_al(uint16_t port) override; virtual void write_port_word(uint16_t port, uint16_t data) override; virtual uint8_t read_byte(uint32_t addr) override; virtual uint16_t read_word(uint32_t addr) override; diff --git a/src/devices/cpu/i86/i86.cpp b/src/devices/cpu/i86/i86.cpp index 0df4308ae79..ddda1000fb9 100644 --- a/src/devices/cpu/i86/i86.cpp +++ b/src/devices/cpu/i86/i86.cpp @@ -671,6 +671,14 @@ void i8086_common_cpu_device::write_port_byte(uint16_t port, uint8_t data) m_io->write_byte(port, data); } +void i8086_common_cpu_device::write_port_byte_al(uint16_t port) +{ + if (port & 1) + m_io->write_word(port-1, swapendian_int16(m_regs.w[AX]), 0xff00); + else + m_io->write_word(port, m_regs.w[AX], 0x00ff); +} + void i8086_common_cpu_device::write_port_word(uint16_t port, uint16_t data) { m_io->write_word_unaligned(port, data); @@ -2068,7 +2076,7 @@ bool i8086_common_cpu_device::common_op(uint8_t op) break; case 0xe6: // i_outal - write_port_byte( fetch(), m_regs.b[AL]); + write_port_byte_al(fetch()); CLK(OUT_IMM8); break; @@ -2139,7 +2147,7 @@ bool i8086_common_cpu_device::common_op(uint8_t op) break; case 0xee: // i_outdxal - write_port_byte(m_regs.w[DX], m_regs.b[AL]); + write_port_byte_al(m_regs.w[DX]); CLK(OUT_DX8); break; diff --git a/src/devices/cpu/i86/i86.h b/src/devices/cpu/i86/i86.h index f08a8e0c2ba..8999fb5b260 100644 --- a/src/devices/cpu/i86/i86.h +++ b/src/devices/cpu/i86/i86.h @@ -139,6 +139,7 @@ protected: virtual uint8_t read_port_byte(uint16_t port); virtual uint16_t read_port_word(uint16_t port); virtual void write_port_byte(uint16_t port, uint8_t data); + virtual void write_port_byte_al(uint16_t port); virtual void write_port_word(uint16_t port, uint16_t data); // Executing instructions diff --git a/src/mame/altos/altos586.cpp b/src/mame/altos/altos586.cpp new file mode 100644 index 00000000000..d6b58b02751 --- /dev/null +++ b/src/mame/altos/altos586.cpp @@ -0,0 +1,774 @@ +// license:BSD-2-Clause +// copyright-holders:Lubomir Rintel + +/*************************************************************************** + + Altos 586 computer emulation + + Work in progress. The current goal is to iron our the overall flaws + caused by my inexperience with C++ and MAME. The ultimate goal is to + make this complete enough for XENIX to run. I guess at that point + it would imply CP/M and OASIS working too. + + At this point I've not tried to boot anything but the diags floppy, + and it's unlikely to work. + + The tests on the diags floppy work well, including serial, clock, + memory management, hard disk & floppy diags and maintenance. + What the diags disk doesn't exercise are interrupts, access control of + bus access from IOP/HDC and system call machinery. + + Literature: + + [1] 586T/986T System Reference Manual, P/N 690-15813-002, April 1985 + This describes a slightly different system. + 690-15813-002_Altos_586T_986T_System_Reference_Apr85.pdf + + [2] Notes on the Altos 586 Computer & Firmware disassembly + https://github.com/lkundrak/altos586/ + +***************************************************************************/ + +#include "emu.h" + +#include "altos586_hdc.h" + +#include "bus/rs232/rs232.h" + +#include "cpu/i86/i86.h" +#include "cpu/z80/z80.h" + +#include "imagedev/floppy.h" + +#include "machine/clock.h" +#include "machine/mm58167.h" +#include "machine/pic8259.h" +#include "machine/pit8253.h" +#include "machine/ram.h" +#include "machine/wd_fdc.h" +#include "machine/z80dma.h" +#include "machine/z80pio.h" +#include "machine/z80sio.h" + +// TODO: Should this be a separate device? It is on a same board. +// I've split this so that I've got two device_memory_interface-s -- +// the board object provides the maps and unmanaged memory and I/O +// spaces, while the MMU provides the managed ones for the bus +// peripherals (IOP and HDC) via its AS_PROGRAM/AS_IO spaces and +// cpu_{mem,io}_{r,w} routines for the access from the main CPU. +// Could there be a better way to structure this? +class altos586_mmu_device : public device_t, public device_memory_interface +{ +public: + using violation_delegate = device_delegate; + + altos586_mmu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + + template + altos586_mmu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, T &&board_tag) + : altos586_mmu_device(mconfig, tag, owner, clock) + { + m_board.set_tag(std::forward(board_tag)); + } + + template void set_violation_callback(T &&... args) { m_violation_callback.set(std::forward(args)...); } + auto syscall_handler() { return m_syscall_handler.bind(); } + + // Managed access from the main board CPU + u16 cpu_io_r(offs_t offset, u16 mem_mask); + void cpu_io_w(offs_t offset, u16 data, u16 mem_mask); + u16 cpu_mem_r(offs_t offset, u16 mem_mask); + void cpu_mem_w(offs_t offset, u16 data, u16 mem_mask); + + // MMU control registers + u16 err_addr1_r(offs_t offset) { return m_err_addr1; } + u16 err_addr2_r(offs_t offset) { return m_err_addr2; } + u16 clr_violation_r(offs_t offset) { return m_violation = 0xffff; } + void clr_violation_w(offs_t offset, u16 data) { clr_violation_r(offset); } + u16 violation_r(offs_t offset) { return m_violation; /* | jumpers */ } + + // Page table SRAM (should this be a ram device instead? + u16 map_ram_r(offs_t offset) { return m_map_ram[offset & 0xff]; } + void map_ram_w(offs_t offset, u16 data, u16 mem_mask) { m_map_ram[offset & 0xff] = data; } + + // Control/Mode + void set_system_mode(); + void check_user_mode(); + void clr_syscall_w(offs_t offset, u8 data); + u16 control_r(offs_t offset); + void control_w(offs_t offset, u16 data); + void cpu_if_w(int state); + +protected: + // device_t implementation + virtual void device_start() override; + + // device_memory_interface implementation + virtual space_config_vector memory_space_config() const override; + +private: + // Managed memory access from the bus (IOP and HDC) + void bus_mem(address_map &map); + u16 bus_mem_r(offs_t offset, u16 mem_mask); + void bus_mem_w(offs_t offset, u16 data, u16 mem_mask); + + // Managed I/O access from the bus (IOP and HDC) + void bus_io(address_map &map); + u16 bus_io_r(offs_t offset, u16 mem_mask); + void bus_io_w(offs_t offset, u16 data, u16 mem_mask); + + // Memory translation and address checking + offs_t phys_mem_addr(offs_t offset); + void signal_violation(u16 violation_bits); + bool check_mem_violation(offs_t offset, int access_bit, int access_bit_set, u16 violation_bits); + + violation_delegate m_violation_callback; + devcb_write_line m_syscall_handler; + + // Access to board's unmanaged address spaces + required_device m_board; + memory_access<20, 1, 0, ENDIANNESS_LITTLE>::specific m_mem; + memory_access<16, 1, 0, ENDIANNESS_LITTLE>::specific m_io; + + // Configuration for managed address spaces we provide + address_space_config m_program_config; + address_space_config m_io_config; + + // User or System mode + bool m_user; + bool m_cpu_if; + u16 m_control; + + u16 m_err_addr1; + u16 m_err_addr2; + + enum : u16 { + INVALID_INSN = 0x0001, // Invalid Instruction + END_OF_STACK = 0x0008, // End of Stack Warning + SYS_W_VIOLATION = 0x0010, // System write Violation + USER_W_VIOLATION = 0x0080, // User Mode Write Violation + IOP_W_VIOLATION = 0x0400, // I/O Processor Write Violation + USER_ACC_VIOLATION = 0x0800, // User Mode Access Violation + }; + u16 m_violation; + + enum { + IOP_W = 11, // Allow I/O Processor Write + SYS_W = 12, // Allow System Write + STACK_BOUND = 13, // Stack Boundary Page + USER_ACC = 14, // Allow User Access + USER_W = 15, // Allow User Write + }; + u16 m_map_ram[256]; +}; + +DEFINE_DEVICE_TYPE(ALTOS586_MMU, altos586_mmu_device, "altos586_mmu", "ALTOS586 MMU") + +altos586_mmu_device::altos586_mmu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, ALTOS586_MMU, tag, owner, clock) + , device_memory_interface(mconfig, *this) + , m_violation_callback(*this) + , m_syscall_handler(*this) + , m_board(*this, finder_base::DUMMY_TAG) + , m_program_config("program", ENDIANNESS_LITTLE, 16, 20, 0, address_map_constructor(FUNC(altos586_mmu_device::bus_mem), this)) + , m_io_config("io", ENDIANNESS_LITTLE, 16, 16, 0, address_map_constructor(FUNC(altos586_mmu_device::bus_io), this)) +{ +} + +u16 altos586_mmu_device::cpu_mem_r(offs_t offset, u16 mem_mask) +{ + if (!machine().side_effects_disabled() && m_user && check_mem_violation(offset, USER_ACC, 1, USER_ACC_VIOLATION)) { + return 0xffff; + } else { + return m_mem.read_word(phys_mem_addr(offset), mem_mask); + } +} + +void altos586_mmu_device::cpu_mem_w(offs_t offset, u16 data, u16 mem_mask) +{ + if (m_user) { + if (check_mem_violation(offset, USER_W, 1, USER_W_VIOLATION)) + return; + if (check_mem_violation(offset, USER_ACC, 1, USER_ACC_VIOLATION)) + return; + } else { + if (check_mem_violation(offset, SYS_W, 1, SYS_W_VIOLATION)) + return; + } + + if ((offset & 0x7ff) < 0x40) { + // Check the stack boundary watermark so that XENIX could map + // some more memory, but don't abort the write cycle. + check_mem_violation(offset, STACK_BOUND, 0, END_OF_STACK); + } + + m_mem.write_word(phys_mem_addr(offset), data, mem_mask); +} + +u16 altos586_mmu_device::cpu_io_r(offs_t offset, u16 mem_mask) +{ + if (!machine().side_effects_disabled() && m_user) { + m_syscall_handler(ASSERT_LINE); + return 0xffff; + } else { + return m_io.read_word(offset << 1, mem_mask); + } +} + +void altos586_mmu_device::cpu_io_w(offs_t offset, u16 data, u16 mem_mask) +{ + if (m_user) { + // XENIX user programs do outb 8800 to trigger the interrupt + // TODO: I have not tested if I got syscall handling right. + m_syscall_handler(ASSERT_LINE); + } else { + m_io.write_word(offset << 1, data, mem_mask); + } +} + +void altos586_mmu_device::bus_mem(address_map &map) +{ + map(0x00000, 0xfffff).rw(FUNC(altos586_mmu_device::bus_mem_r), FUNC(altos586_mmu_device::bus_mem_w)); +} + +u16 altos586_mmu_device::bus_mem_r(offs_t offset, u16 mem_mask) +{ + return m_mem.read_word(phys_mem_addr(offset), mem_mask); +} + +void altos586_mmu_device::bus_mem_w(offs_t offset, u16 data, u16 mem_mask) +{ + if (check_mem_violation(offset, IOP_W, 1, IOP_W_VIOLATION)) { + return; + } else { + m_mem.write_word(phys_mem_addr(offset), data, mem_mask); + } +} + +void altos586_mmu_device::bus_io(address_map &map) +{ + // As per the manual, I/O addresses up to 0x3ff are available only to CPU, not other bus masters + map(0x0400, 0xffff).rw(FUNC(altos586_mmu_device::bus_io_r), FUNC(altos586_mmu_device::bus_io_w)); +} + +u16 altos586_mmu_device::bus_io_r(offs_t offset, u16 mem_mask) +{ + return m_io.read_word(offset, mem_mask); +} + +void altos586_mmu_device::bus_io_w(offs_t offset, u16 data, u16 mem_mask) +{ + m_io.write_word(offset, data, mem_mask); +} + +void altos586_mmu_device::device_start() +{ + set_system_mode(); + m_cpu_if = false; + + m_board->space(AS_PROGRAM).specific(m_mem); + m_board->space(AS_IO).specific(m_io); + + m_violation_callback.resolve_safe(); + + save_item(NAME(m_user)); + save_item(NAME(m_cpu_if)); + save_item(NAME(m_control)); + save_item(NAME(m_err_addr1)); + save_item(NAME(m_err_addr2)); + save_item(NAME(m_violation)); + save_item(NAME(m_map_ram)); +} + +device_memory_interface::space_config_vector altos586_mmu_device::memory_space_config() const +{ + return space_config_vector { + std::make_pair(AS_PROGRAM, &m_program_config), + std::make_pair(AS_IO, &m_io_config) }; +} + +offs_t altos586_mmu_device::phys_mem_addr(offs_t offset) +{ + return ((m_map_ram[offset >> 11] & 0xff) << 12) | ((offset & 0x7ff) << 1); +} + +void altos586_mmu_device::signal_violation(u16 violation_bits) +{ + u16 old_violation = m_violation; + m_violation &= ~violation_bits; + if (old_violation == 0xffff && BIT(m_control, 2)) { + // TODO: Is this (user mode request) bit actually cleared on real hw? + // Is this the right place to do that? + m_control &= ~1; + m_violation_callback(); + } +} + +bool altos586_mmu_device::check_mem_violation(offs_t offset, int access_bit, int access_bit_set, u16 violation_bits) +{ + u16 acc = m_map_ram[offset >> 11]; + + if (BIT(acc, access_bit) == access_bit_set) { + return false; + } else { + if (m_violation == 0xffff) { + // TODO: Would it be less clumsy to move setting + // of m_err_addr* into signal_violation()? + m_err_addr1 = offset << 1; + m_err_addr2 = (offset >> 3) & 0xf000; + + if (m_user) + m_err_addr2 |= 0x0100; + // TODO: Warm start bit. When is it set? + m_err_addr2 |= 0x0200; + if (BIT(m_control, 2)) + m_err_addr2 |= 0x0800; // NMI enabled + } + signal_violation(violation_bits); + return true; + } +} + +void altos586_mmu_device::set_system_mode() +{ + m_user = false; +} + +void altos586_mmu_device::check_user_mode() +{ + if (m_cpu_if && BIT(m_control, 0)) + m_user = true; +} + +void altos586_mmu_device::clr_syscall_w(offs_t offset, u8 data) +{ + m_syscall_handler(CLEAR_LINE); +} + +u16 altos586_mmu_device::control_r(offs_t offset) +{ + // TODO: Is this register actually readable? + return m_control; +} + +void altos586_mmu_device::control_w(offs_t offset, u16 data) +{ + m_control = data; + check_user_mode(); +} + +void altos586_mmu_device::cpu_if_w(int state) +{ + if (state == ASSERT_LINE) { + m_cpu_if = true; + check_user_mode(); + } else if (m_user) { + if (m_violation == 0xffff) { + m_err_addr1 = 0; + m_err_addr2 = 0; + } + signal_violation(INVALID_INSN); + } +} + +namespace { + +class altos586_state : public driver_device, public device_memory_interface { +public: + altos586_state(const machine_config &mconfig, device_type type, const char *tag) + : driver_device(mconfig, type, tag) + , device_memory_interface(mconfig, *this) + , m_program_config("program", ENDIANNESS_LITTLE, 16, 20, 0, address_map_constructor(FUNC(altos586_state::mem_map), this)) + , m_io_config("io", ENDIANNESS_LITTLE, 16, 16, 0, address_map_constructor(FUNC(altos586_state::io_map), this)) + , m_cpu(*this, "cpu") + , m_mmu(*this, "mmu") + , m_ram(*this, RAM_TAG) + , m_pic(*this, "pic8259") + , m_pit(*this, "pit") + , m_iop(*this, "iop") + , m_fdc(*this, "fd1797") + , m_floppy(*this, "fd1797:%u", 0) + { + } + + void altos586(machine_config &config); + +protected: + // driver_device implementation + virtual void machine_start() override; + + // device_memory_interface implementation + virtual space_config_vector memory_space_config() const override; + +private: + void mmu_violation(); + + // IOP interfacing. + void iop_attn_w(offs_t offset, u16 data); + u8 hostram_r(offs_t offset); + void hostram_w(offs_t offset, u8 data); + void hiaddr_w(u8 data); + u8 pio_pa_r(offs_t offset); + void pio_pa_w(offs_t offset, u8 data); + void pio_pb_w(offs_t offset, u8 data); + + IRQ_CALLBACK_MEMBER(inta_cb); + + // Maps for the 8086 & Z80 processors + void cpu_mem(address_map &map) ATTR_COLD; + void cpu_io(address_map &map) ATTR_COLD; + void iop_mem(address_map &map) ATTR_COLD; + void iop_io(address_map &map) ATTR_COLD; + + // Maps and physical spaces used by the MMU + void mem_map(address_map &map) ATTR_COLD; + void io_map(address_map &map) ATTR_COLD; + address_space_config m_program_config; + address_space_config m_io_config; + + required_device m_cpu; + required_device m_mmu; + required_device m_ram; + required_device m_pic; + required_device m_pit; + required_device m_iop; + required_device m_fdc; + required_device_array m_floppy; + + u8 m_hiaddr; + + memory_access<20, 1, 0, ENDIANNESS_LITTLE>::specific m_mmu_mem; +}; + +void altos586_state::mem_map(address_map &map) +{ + // ROM needs to be there for IOP bootup + map(0xfc000, 0xfffff).rom().region("bios", 0); +} + +void altos586_state::io_map(address_map &map) +{ + map(0x0040, 0x0047).w(m_mmu, FUNC(altos586_mmu_device::clr_syscall_w)); + // TODO: Manual reads: 0048H to 004FH Channel attention 0 + // (reserved for future bus master channel attentions) + map(0x0050, 0x0057).w(FUNC(altos586_state::iop_attn_w)); + map(0x0058, 0x005f).rw(m_mmu, FUNC(altos586_mmu_device::control_r), FUNC(altos586_mmu_device::control_w)); + map(0x0060, 0x0067).r(m_mmu, FUNC(altos586_mmu_device::err_addr2_r)); + map(0x0068, 0x006F).r(m_mmu, FUNC(altos586_mmu_device::err_addr1_r)); + map(0x0070, 0x0077).rw(m_mmu, FUNC(altos586_mmu_device::clr_violation_r), FUNC(altos586_mmu_device::clr_violation_w)); + map(0x0078, 0x007f).r(m_mmu, FUNC(altos586_mmu_device::violation_r)); + + // These are wired funnily + map(0x0080, 0x00ff).lrw16( + NAME([this] (offs_t offset) { return m_pic->read(~offset & 1); }), + NAME([this] (offs_t offset, u16 data) { m_pic->write(~offset & 1, data); })); + map(0x0100, 0x01ff).lrw16( + NAME([this] (offs_t offset) { return m_pit->read(~offset) << 8; }), + NAME([this] (offs_t offset, u16 data) { m_pit->write(~offset, data >> 8); })); + + map(0x0200, 0x03ff).rw(m_mmu, FUNC(altos586_mmu_device::map_ram_r), FUNC(altos586_mmu_device::map_ram_w)); + + // Addresses 0400H to FFFFH are, unlike the above, accessible from + // other bus masters than the main CPU. Peripherals are expected to + // hook there. Documented as "Reserved for system bus I/O" + + // Bus peripherals + map(0xff00, 0xff01).w("hdc", FUNC(altos586_hdc_device::attn_w)); +} + +void altos586_state::cpu_mem(address_map &map) +{ + map(0x00000, 0xfffff).rw(m_mmu, FUNC(altos586_mmu_device::cpu_mem_r), FUNC(altos586_mmu_device::cpu_mem_w)); + // ROM is always mapped at these addresses + map(0xfc000, 0xfffff).rom().region("bios", 0); +} + +void altos586_state::cpu_io(address_map &map) +{ + map(0x0000, 0xffff).rw(m_mmu, FUNC(altos586_mmu_device::cpu_io_r), FUNC(altos586_mmu_device::cpu_io_w)); +} + +void altos586_state::iop_mem(address_map &map) +{ + map(0x0000, 0x1fff).rom().region("iop", 0); + map(0x2000, 0x27ff).ram(); + map(0x8000, 0xffff).rw(FUNC(altos586_state::hostram_r), FUNC(altos586_state::hostram_w)); +} + +void altos586_state::iop_io(address_map &map) +{ + map.global_mask(0xff); + + map(0x00, 0x00).w(FUNC(altos586_state::hiaddr_w)); // 0x00 Address Latch + map(0x20, 0x23).rw("iop_pit0", FUNC(pit8254_device::read), FUNC(pit8254_device::write)); // 0x20 PIT 0 + map(0x24, 0x27).rw("iop_pit1", FUNC(pit8254_device::read), FUNC(pit8254_device::write)); // 0x24 PIT 1 + map(0x28, 0x2b).rw("iop_sio0", FUNC(z80sio_device::ba_cd_r), FUNC(z80sio_device::ba_cd_w)); // 0x28 SIO 0 + map(0x2c, 0x2f).rw("iop_sio1", FUNC(z80sio_device::ba_cd_r), FUNC(z80sio_device::ba_cd_w)); // 0x2C SIO 1 + map(0x30, 0x33).rw("iop_sio2", FUNC(z80sio_device::ba_cd_r), FUNC(z80sio_device::ba_cd_w)); // 0x30 SIO 2 + map(0x34, 0x37).rw("iop_pio", FUNC(z80pio_device::read_alt), FUNC(z80pio_device::write_alt)); // 0x34 PIO + map(0x38, 0x3b).rw(m_fdc, FUNC(fd1797_device::read), FUNC(fd1797_device::write)); // 0x38 FDC + map(0x3c, 0x3f).rw("iop_dma", FUNC(z80dma_device::read), FUNC(z80dma_device::write)); // 0x3C DMA + map(0x40, 0x40).noprw(); // 0x40 DMA - Clear carrier sense and parity error bit + map(0x80, 0x9f).rw("iop_rtc", FUNC(mm58167_device::read), FUNC(mm58167_device::write)); // 0x80 RTC - Counter - thousandths of seconds + // 0x60 586T Generate MULTIBUS interrupt +} + +void altos586_state::iop_attn_w(offs_t offset, u16 data) +{ + m_iop->set_input_line(INPUT_LINE_NMI, ASSERT_LINE); + m_iop->set_input_line(INPUT_LINE_NMI, CLEAR_LINE); +} + +u8 altos586_state::hostram_r(offs_t offset) +{ + return m_mmu_mem.read_byte((m_hiaddr << 15) | (offset & 0x7fff)); +} + +void altos586_state::hostram_w(offs_t offset, u8 data) +{ + m_mmu_mem.write_byte((m_hiaddr << 15) | (offset & 0x7fff), data); +} + +void altos586_state::hiaddr_w(u8 data) +{ + m_hiaddr = data; +} + +u8 altos586_state::pio_pa_r(offs_t offset) +{ + u8 data = 0x00; + + data |= 0 << 0; // Positions 2-4 in E1 jumper (present on my machine, pulling to GND) + data |= 1 << 1; // Positions 1-2 in E1 jumper (absent, pull-up resistor) + data |= 1 << 2; // Positions 7-8 in E1 jumper (absent, pull-up resistor) + data |= 0 << 3; // Pulled to GND + data |= 1 << 4; // Pin 18 of 74LS74 at position G9 + data |= 0 << 5; // TODO: pin 22 (IR4) of 8259 PIC. Remember what we set it to. + data |= 1 << 6; // Pin 5 of 74LS38 at position H9 + data |= m_fdc->intrq_r() << 7; + return data; +} + +void altos586_state::pio_pa_w(offs_t offset, u8 data) +{ + // TODO: Is there an interrerrupt ack pin somewhere? PA4 or PA6 perhaps? + m_pic->ir4_w(BIT(data, 5)); +} + +void altos586_state::pio_pb_w(offs_t offset, u8 data) +{ + floppy_image_device *floppy; + + if (!BIT(data, 3)) + floppy = m_floppy[0]->get_device(); + else if (!BIT(data, 4)) + floppy = m_floppy[1]->get_device(); + else + floppy = nullptr; + + if (floppy) { + floppy->mon_w(0); + floppy->ss_w(BIT(data, 5)); + } + + m_fdc->set_floppy(floppy); + m_fdc->dden_w(BIT(data, 6)); +} + +IRQ_CALLBACK_MEMBER(altos586_state::inta_cb) +{ + m_mmu->set_system_mode(); + return m_pic->acknowledge(); +} + +void altos586_state::machine_start() +{ + u8 *romdata = memregion("bios")->base(); + int romlen = memregion("bios")->bytes(); + + space(AS_PROGRAM).install_ram(0, m_ram->size() - 1, m_ram->pointer()); + + // The address lines to the ROMs are reversed + std::reverse(romdata, romdata + romlen); + + m_mmu->space(AS_PROGRAM).specific(m_mmu_mem); + + save_item(NAME(m_hiaddr)); +} + +device_memory_interface::space_config_vector altos586_state::memory_space_config() const +{ + // Spaces we provide the MMU + return space_config_vector { + std::make_pair(AS_PROGRAM, &m_program_config), + std::make_pair(AS_IO, &m_io_config) }; +} + +static void altos586_floppies(device_slot_interface &device) +{ + device.option_add("525hd", FLOPPY_525_HD); +} + +static DEVICE_INPUT_DEFAULTS_START(altos586_terminal) + DEVICE_INPUT_DEFAULTS( "RS232_TXBAUD", 0xff, RS232_BAUD_9600 ) + DEVICE_INPUT_DEFAULTS( "RS232_RXBAUD", 0xff, RS232_BAUD_9600 ) + DEVICE_INPUT_DEFAULTS( "RS232_DATABITS", 0xff, RS232_DATABITS_8 ) + DEVICE_INPUT_DEFAULTS( "RS232_PARITY", 0xff, RS232_PARITY_NONE ) + DEVICE_INPUT_DEFAULTS( "RS232_STOPBITS", 0xff, RS232_STOPBITS_1 ) +DEVICE_INPUT_DEFAULTS_END + +void altos586_state::mmu_violation() +{ + m_mmu->set_system_mode(); + m_cpu->set_input_line(INPUT_LINE_NMI, ASSERT_LINE); + m_cpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE); +} + +void altos586_state::altos586(machine_config &config) +{ + ALTOS586_MMU(config, m_mmu, 0, *this); + m_mmu->set_violation_callback(FUNC(altos586_state::mmu_violation)); + m_mmu->syscall_handler().set(m_pic, FUNC(pic8259_device::ir0_w)); + + I8086(config, m_cpu, 30_MHz_XTAL / 3); + m_cpu->set_addrmap(AS_PROGRAM, &altos586_state::cpu_mem); + m_cpu->set_addrmap(AS_IO, &altos586_state::cpu_io); + m_cpu->set_irq_acknowledge_callback(FUNC(altos586_state::inta_cb)); + m_cpu->if_handler().set(m_mmu, FUNC(altos586_mmu_device::cpu_if_w)); + + RAM(config, RAM_TAG).set_default_size("512K").set_extra_options("1M"); + + PIC8259(config, m_pic); + m_pic->out_int_callback().set_inputline(m_cpu, INPUT_LINE_IRQ0); + + PIT8254(config, m_pit); + m_pit->set_clk<0>(30_MHz_XTAL/6); + m_pit->out_handler<0>().set("iop_sio2", FUNC(z80sio_device::rxcb_w)); + m_pit->out_handler<0>().append("iop_sio2", FUNC(z80sio_device::txcb_w)); + m_pit->set_clk<1>(30_MHz_XTAL/6); + m_pit->set_clk<2>(62'500); + m_pit->out_handler<2>().set(m_pic, FUNC(pic8259_device::ir1_w)); + + Z80(config, m_iop, 8_MHz_XTAL / 2); + m_iop->set_addrmap(AS_PROGRAM, &altos586_state::iop_mem); + m_iop->set_addrmap(AS_IO, &altos586_state::iop_io); + + pit8254_device &pit0(PIT8254(config, "iop_pit0", 0)); + pit0.set_clk<0>(30_MHz_XTAL); + pit0.out_handler<0>().set("iop_sio0", FUNC(z80sio_device::rxca_w)); + pit0.out_handler<0>().append("iop_sio0", FUNC(z80sio_device::txca_w)); + pit0.set_clk<1>(30_MHz_XTAL/6); + pit0.out_handler<1>().set("iop_sio0", FUNC(z80sio_device::rxcb_w)); + pit0.out_handler<1>().append("iop_sio0", FUNC(z80sio_device::txcb_w)); + pit0.set_clk<2>(30_MHz_XTAL/6); + pit0.out_handler<2>().set("iop_sio1", FUNC(z80sio_device::rxca_w)); + pit0.out_handler<2>().append("iop_sio1", FUNC(z80sio_device::txca_w)); + + pit8254_device &pit1(PIT8254(config, "iop_pit1", 0)); + pit1.set_clk<0>(30_MHz_XTAL/6); + pit1.out_handler<0>().set("iop_sio1", FUNC(z80sio_device::rxcb_w)); + pit1.out_handler<0>().append("iop_sio1", FUNC(z80sio_device::txcb_w)); + pit1.set_clk<1>(30_MHz_XTAL/6); + pit1.out_handler<1>().set("iop_sio2", FUNC(z80sio_device::rxca_w)); + pit1.out_handler<1>().append("iop_sio2", FUNC(z80sio_device::txca_w)); + + z80sio_device &sio0(Z80SIO(config, "iop_sio0", 8_MHz_XTAL / 2)); + sio0.out_txda_callback().set("rs232_port3", FUNC(rs232_port_device::write_txd)); + sio0.out_dtra_callback().set("rs232_port3", FUNC(rs232_port_device::write_dtr)); + sio0.out_rtsa_callback().set("rs232_port3", FUNC(rs232_port_device::write_rts)); + sio0.out_txdb_callback().set("rs232_port4", FUNC(rs232_port_device::write_txd)); + sio0.out_dtrb_callback().set("rs232_port4", FUNC(rs232_port_device::write_dtr)); + sio0.out_rtsb_callback().set("rs232_port4", FUNC(rs232_port_device::write_rts)); + sio0.out_int_callback().set_inputline(m_iop, INPUT_LINE_IRQ0); + sio0.set_cputag(m_iop); + + rs232_port_device &rs232_port3(RS232_PORT(config, "rs232_port3", default_rs232_devices, nullptr)); + rs232_port3.rxd_handler().set("iop_sio0", FUNC(z80sio_device::rxa_w)); + rs232_port3.dcd_handler().set("iop_sio0", FUNC(z80sio_device::dcda_w)); + rs232_port3.cts_handler().set("iop_sio0", FUNC(z80sio_device::ctsa_w)); + + rs232_port_device &rs232_port4(RS232_PORT(config, "rs232_port4", default_rs232_devices, nullptr)); + rs232_port4.rxd_handler().set("iop_sio0", FUNC(z80sio_device::rxb_w)); + rs232_port4.dcd_handler().set("iop_sio0", FUNC(z80sio_device::dcdb_w)); + rs232_port4.cts_handler().set("iop_sio0", FUNC(z80sio_device::ctsb_w)); + + z80sio_device &sio1(Z80SIO(config, "iop_sio1", 8_MHz_XTAL / 2)); + sio1.out_txda_callback().set("rs232_port1", FUNC(rs232_port_device::write_txd)); + sio1.out_dtra_callback().set("rs232_port1", FUNC(rs232_port_device::write_dtr)); + sio1.out_rtsa_callback().set("rs232_port1", FUNC(rs232_port_device::write_rts)); + sio1.out_txdb_callback().set("rs232_port2", FUNC(rs232_port_device::write_txd)); + sio1.out_dtrb_callback().set("rs232_port2", FUNC(rs232_port_device::write_dtr)); + sio1.out_rtsb_callback().set("rs232_port2", FUNC(rs232_port_device::write_rts)); + sio1.out_int_callback().set_inputline(m_iop, INPUT_LINE_IRQ0); + sio1.set_cputag(m_iop); + + rs232_port_device &rs232_port1(RS232_PORT(config, "rs232_port1", default_rs232_devices, "terminal")); + rs232_port1.rxd_handler().set("iop_sio1", FUNC(z80sio_device::rxa_w)); + rs232_port1.dcd_handler().set("iop_sio1", FUNC(z80sio_device::dcda_w)); + rs232_port1.cts_handler().set("iop_sio1", FUNC(z80sio_device::ctsa_w)); + rs232_port1.set_option_device_input_defaults("terminal", DEVICE_INPUT_DEFAULTS_NAME(altos586_terminal)); + + rs232_port_device &rs232_port2(RS232_PORT(config, "rs232_port2", default_rs232_devices, nullptr)); + rs232_port2.rxd_handler().set("iop_sio1", FUNC(z80sio_device::rxb_w)); + rs232_port2.dcd_handler().set("iop_sio1", FUNC(z80sio_device::dcdb_w)); + rs232_port2.cts_handler().set("iop_sio1", FUNC(z80sio_device::ctsb_w)); + + z80sio_device &sio2(Z80SIO(config, "iop_sio2", 8_MHz_XTAL/2)); + sio2.out_txda_callback().set("rs232_port5", FUNC(rs232_port_device::write_txd)); + sio2.out_dtra_callback().set("rs232_port5", FUNC(rs232_port_device::write_dtr)); + sio2.out_rtsa_callback().set("rs232_port5", FUNC(rs232_port_device::write_rts)); + sio2.out_txdb_callback().set("rs232_port6", FUNC(rs232_port_device::write_txd)); + sio2.out_dtrb_callback().set("rs232_port6", FUNC(rs232_port_device::write_dtr)); + sio2.out_rtsb_callback().set("rs232_port6", FUNC(rs232_port_device::write_rts)); + sio2.set_cputag(m_iop); + + rs232_port_device &rs232_port5(RS232_PORT(config, "rs232_port5", default_rs232_devices, nullptr)); + rs232_port5.rxd_handler().set("iop_sio2", FUNC(z80sio_device::rxa_w)); + rs232_port5.dcd_handler().set("iop_sio2", FUNC(z80sio_device::dcda_w)); + rs232_port5.cts_handler().set("iop_sio2", FUNC(z80sio_device::ctsa_w)); + + rs232_port_device &rs232_port6(RS232_PORT(config, "rs232_port6", default_rs232_devices, nullptr)); + rs232_port6.rxd_handler().set("iop_sio2", FUNC(z80sio_device::rxb_w)); + rs232_port6.dcd_handler().set("iop_sio2", FUNC(z80sio_device::dcdb_w)); + rs232_port6.cts_handler().set("iop_sio2", FUNC(z80sio_device::ctsb_w)); + + z80pio_device &pio(Z80PIO(config, "iop_pio", 8_MHz_XTAL / 2)); + pio.in_pa_callback().set(FUNC(altos586_state::pio_pa_r)); + pio.out_pa_callback().set(FUNC(altos586_state::pio_pa_w)); + pio.out_pb_callback().set(FUNC(altos586_state::pio_pb_w)); + + FD1797(config, m_fdc, 1'000'000); // TODO: Check clock + m_fdc->drq_wr_callback().set("iop_dma", FUNC(z80dma_device::rdy_w)).invert(); + FLOPPY_CONNECTOR(config, m_floppy[0], altos586_floppies, "525hd", floppy_image_device::default_mfm_floppy_formats); // TODO: Sound? + FLOPPY_CONNECTOR(config, m_floppy[1], altos586_floppies, "525hd", floppy_image_device::default_mfm_floppy_formats); + + z80dma_device &dma(Z80DMA(config, "iop_dma", 8_MHz_XTAL / 2)); + dma.in_mreq_callback().set([this] (offs_t offset) { return m_iop->space(AS_PROGRAM).read_byte(offset); }); + dma.out_mreq_callback().set([this] (offs_t offset, u8 data) { m_iop->space(AS_PROGRAM).write_byte(offset, data); }); + dma.in_iorq_callback().set([this] (offs_t offset) { return m_iop->space(AS_IO).read_byte(offset); }); + dma.out_iorq_callback().set([this] (offs_t offset, u8 data) { m_iop->space(AS_IO).write_byte(offset, data); }); + + // TODO: The RTC seems to run approx. 2 times slower. Why? + MM58167(config, "iop_rtc", 32.768_kHz_XTAL); + + // TODO: Could a multibus-like bus interface be implemented? + // This sits on a such bus, but the "backplane" are two 50-pin + // ribbon cables w/o a fixed number of slots. + ALTOS586_HDC(config, "hdc", 30_MHz_XTAL / 3, m_mmu); + + SOFTWARE_LIST(config, "flop_list").set_original("altos586"); +} + +ROM_START(altos586) + ROM_REGION16_LE(0x4000, "bios", 0) + ROMX_LOAD("altos586-v13-g1.rom", 0x0001, 0x1000, CRC(29fdcb40) SHA1(34700603d6034f0ccc599d74432cef332459987b) , ROM_SKIP(1)) + ROMX_LOAD("altos586-v13-g2.rom", 0x0000, 0x1000, CRC(de22003a) SHA1(ce25a45cd4fb0ff63e5064fb74347b978c3a78f1) , ROM_SKIP(1)) + ROM_COPY("bios", 0, 0x2000, 0x2000) // v1.3 Firmware is 2x4K, but the board accepts 2x8K + + ROM_REGION(0x2000, "iop", 0) + ROM_LOAD("altos586-v56-iop.rom", 0x0000, 0x2000, CRC(411ca183) SHA1(f2af03d361dddbf6aae055e69210c994ead281d8)) +ROM_END + +} // anonymous namespace + +COMP( 1984, altos586, 0, 0, altos586, 0, altos586_state, empty_init, "Altos Computer Systems", "ACS586", MACHINE_NO_SOUND_HW | MACHINE_NOT_WORKING) diff --git a/src/mame/altos/altos586_hdc.cpp b/src/mame/altos/altos586_hdc.cpp new file mode 100644 index 00000000000..37bef261b1c --- /dev/null +++ b/src/mame/altos/altos586_hdc.cpp @@ -0,0 +1,381 @@ +// license:BSD-2-Clause +// copyright-holders:Lubomir Rintel + +/*************************************************************************** + + Altos 586 Hard Disk Controller emulation + + The controller is based around the 8089 I/O Processor. + Its memory bus is connected (via modified multibus) to the main memory, + shared with the main 8086 processor. The I/O bus contains the internal + controller registers and a 16K SRAM for caching the I/O programs + I/O parameter blocks (command blocks) and staging the sector data. + + The formatting is custom, enforces 16 sectors per tracks. + Heads/Cylinder combinations used by different Altos 586 models + as supported by the diagnostic software (and XENIX, I think): + + Model Heads Cylinders + ACS586-10 4 306 + ACS586-20 6 306 + ACS586-30 6 512 + ACS586-40 8 512 + H-H 20 MB 4 612 + + Literature: + + [1] Notes on the Altos 586 Computer & Firmware disassembly + https://github.com/lkundrak/altos586/ + + [2] Preliminary 8600 User Manual + Section 7. System Specs. + 4.5. Rigid Disk Controllers and Interface + Specification Revision 4.2, May 27, 1982, Page 52-62 + + [3] Preliminary 8600 User Manual + Section 7. System Specs. + A. Rigid Disk Controller Documentation + Specification Revision 4.2, May 27, 1982, Page 95-108 + + Note: The Altos 8600 manuals [2] and [3] describe a controller + wired differently (the 8089 seems to be in local mode, not remote), + the register addresses are different, but their function and command + set seems very different. I've written this before discovering those + manuals, so the command names are slightly different. My intent is to + eventually align that and review some of my assumptions too. + + TODO: How is the command completion interrupt signalled? + I've only verified it to work with the diags floppy and that + one is not interrupt-driven. XENIX almost certainly is. + +***************************************************************************/ + +#include "emu.h" +#include "altos586_hdc.h" + +//#define VERBOSE 1 +#include "logmacro.h" + +DEFINE_DEVICE_TYPE(ALTOS586_HDC, altos586_hdc_device, "altos586_hdc", "Disk Controller board for Altos 586") + +altos586_hdc_device::altos586_hdc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, ALTOS586_HDC, tag, owner, clock) + , m_bus(*this, finder_base::DUMMY_TAG, -1) + , m_iop(*this, "iop") + , m_hdd(*this, "hdd%u", 0) +{ +} + +bool altos586_hdc_device::sector_exists(uint8_t index) +{ + if (!m_geom[m_drive]) { + logerror("drive not present: %d", m_drive); + } else if (m_head > m_geom[m_drive]->heads) { + logerror("head %d not present in drive that has %d heads", m_head, m_geom[m_drive]->heads); + } else if (index > m_geom[m_drive]->sectors) { + logerror("sector %d not present in drive that has %d sectors per track", index, m_geom[m_drive]->sectors); + } else { + LOG("drive %d sector CHS=%d/%d/%d found\n", m_drive, m_cyl[m_drive], m_head, index); + return true; + } + + m_status |= 0x40; // Record not found. + return false; +} + +uint32_t altos586_hdc_device::sector_lba(uint8_t const index) +{ + return (m_cyl[m_drive] * m_geom[m_drive]->heads + m_head) * m_geom[m_drive]->sectors + index; +} + +void altos586_hdc_device::sector_read(uint8_t index) +{ + if (!sector_exists(index)) + return; + m_hdd[m_drive]->read(sector_lba(index), &m_sector[5]); + m_iop->drq1_w(ASSERT_LINE); +} + +void altos586_hdc_device::sector_write(uint8_t index) +{ + if (!sector_exists(index)) + return; + m_hdd[m_drive]->write(sector_lba(index), &m_sector[5]); +} + +uint16_t altos586_hdc_device::mem_r(offs_t offset, uint16_t mem_mask) +{ + return m_bus->read_word(offset << 1, mem_mask); +} + +void altos586_hdc_device::mem_w(offs_t offset, uint16_t data, uint16_t mem_mask) +{ + m_bus->write_word(offset << 1, data, mem_mask); +} + +void altos586_hdc_device::altos586_hdc_mem(address_map &map) +{ + map(0x00000, 0xfffff).rw(FUNC(altos586_hdc_device::mem_r), FUNC(altos586_hdc_device::mem_w)); +} + +uint16_t altos586_hdc_device::data_r(offs_t offset) +{ + uint8_t value = m_sector[m_secoffset]; + + if (!machine().side_effects_disabled()) { + m_secoffset++; + m_secoffset %= std::size(m_sector); + + if (m_secoffset == 0) { + LOG("read reached the end of the data buffer\n"); + m_iop->drq1_w(CLEAR_LINE); + } + } + + return value; +} + +void altos586_hdc_device::data_w(offs_t offset, uint16_t data) +{ + m_sector[m_secoffset++] = data; + m_secoffset %= std::size(m_sector); + + if (m_secoffset == 5) { + // If we reached this watermark in the data buffer, we've completed + // writing the sector formatting data. + // We don't do anything the data beyond checking it. + if (m_sector[1] != 0xfe) + logerror("suspicious sector mark\n"); + if ((((m_sector[3] & 0xf) << 8) | m_sector[2]) != m_cyl[m_drive]) + logerror("cylinder number mismatch\n"); + if ((m_sector[3] >> 4) != m_head) + logerror("head number mismatch\n"); + if (m_sector[0] != m_sector[4]) { + // I think (not sure), that this might be okay for interleaved access. + LOG("sector number mismatch (probably okay)\n"); + } + + LOG("writing drive %d sector CHS=%d/%d/%d format finished\n", m_drive, m_cyl[m_drive], m_head, m_sector[0]); + LOG(" sector mark = 0x%02x\n", m_sector[1]); + LOG(" cylinder low = 0x%02x\n", m_sector[2]); + LOG(" head | cylinder hi = 0x%02x\n", m_sector[3]); + LOG(" sector = 0x%02x\n", m_sector[4]); + + m_iop->drq1_w(CLEAR_LINE); + m_secoffset = 0; + } else if (m_secoffset == 0) { + LOG("write reached the end of the data buffer\n"); + m_iop->drq1_w(CLEAR_LINE); + sector_write(m_sector[0]); + } +} + +void altos586_hdc_device::head_select_w(offs_t offset, uint16_t data) +{ + // Not Ready. + m_status &= ~0x80; + + switch (data & 0xf0) { + case 0x10: + m_drive = 0; + break; + case 0x20: + m_drive = 1; + break; + default: + logerror("unsupported drive select\n"); + return; + } + + if (m_geom[m_drive] == nullptr) { + logerror("drive %d not present\n", m_drive); + return; + } + + m_head = data & 0x0f; + if (m_head > m_geom[m_drive]->heads) { + logerror("head %d invalid for drive %d\n", m_head, m_drive); + return; + } + + // Ready. + m_status |= 0x80; + + LOG("selected drive %d head %d\n", m_drive, m_head); +} + +uint16_t altos586_hdc_device::seek_status_r(offs_t offset) +{ + return m_seek_status; +} + +void altos586_hdc_device::cyl_w(offs_t offset, uint16_t data) +{ + m_cyl_latch >>= 8; + m_cyl_latch |= data << 8; +} + +uint16_t altos586_hdc_device::status_r(offs_t offset) +{ + return m_status; +} + +void altos586_hdc_device::command_w(offs_t offset, uint16_t data) +{ + switch (data) { + case 0x01: + // Read. + // Sector number now in data buffer, data readout follows. + LOG("READ command\n"); + if (m_secoffset != 1) + logerror("expected one value in data buffer, has %d\n", m_secoffset); + + sector_read(m_sector[0]); + // Skip the sector format, just read the data. + m_secoffset = 5; + break; + + case 0x01 | 0x08: + // Long Read. + // Sector number now in data buffer, header + data readout follows. + LOG("READ LONG command\n"); + if (m_secoffset != 1) + logerror("expected one value in data buffer, has %d\n", m_secoffset); + + // Sector header. + m_sector[2] = m_sector[0]; + m_sector[0] = m_cyl[m_drive] & 0xff; + // Bit 3 indicates bad sector (we don't ever set it). + m_sector[1] = m_head << 4 | m_cyl[m_drive] >> 8; + m_sector[3] = m_sector[4] = 0; + + sector_read(m_sector[2]); + m_secoffset = 0; + break; + + case 0x02: + // Write. + // Sector number now in data buffer, data write follows. + LOG("WRITE command\n"); + if (m_secoffset != 1) + logerror("expected one value in data buffer, has %d\n", m_secoffset); + + // No sector format, just data. + m_secoffset = 5; + m_iop->drq1_w(ASSERT_LINE); + break; + + case 0x04: + // Write Sector Format. + // Sector number now in data buffer, header write follows. + LOG("WRITE FORMAT command\n"); + if (m_secoffset != 1) + logerror("expected one value in data buffer, has %d\n", m_secoffset); + + m_secoffset = 1; // Leave the sector number in. + m_iop->drq1_w(ASSERT_LINE); + break; + + case 0x10: + // Seek to cylinder. + // Current cylinder is in data buffer, new one in a separate latch. + LOG("SEEK from cylinder %d to %d\n", m_sector[1] << 8 | m_sector[0], m_cyl_latch); + if (m_secoffset != 2) + logerror("expected two values in data buffer, has %d\n", m_secoffset); + + m_secoffset = 0; + m_cyl[m_drive] = m_cyl_latch; + // Seek done. + m_seek_status |= 0x02; + // Not busy. + m_status &= ~0x01; + break; + + case 0x20: + // Not sure, actually. + LOG("SELECT command\n"); + // Head/drive selected? + m_seek_status |= 0x01; + break; + + case 0x80: + // This looks like a reset of some sort. + LOG("RESET command\n"); + device_reset(); + break; + + default: + logerror("unknown command 0x%02x\n", data); + } +} + +void altos586_hdc_device::altos586_hdc_io(address_map &map) +{ + map(0x0000, 0x3fff).ram(); // 16K SRAM. + map(0xffd0, 0xffd1).rw(FUNC(altos586_hdc_device::data_r), FUNC(altos586_hdc_device::data_w)); + map(0xffd2, 0xffd3).w(FUNC(altos586_hdc_device::head_select_w)); + map(0xffd4, 0xffd5).rw(FUNC(altos586_hdc_device::seek_status_r), FUNC(altos586_hdc_device::cyl_w)); + map(0xffd6, 0xffd7).rw(FUNC(altos586_hdc_device::status_r), FUNC(altos586_hdc_device::command_w)); + // Diags write 0x10 to 0xfff8 and XENIX writes 0x82 to 0xffe6. + // Not sure what either does. + map(0xfff8, 0xfff9).nopw(); +} + +template +std::error_condition altos586_hdc_device::hdd_load(device_image_interface &image) +{ + if (m_hdd[Index]->get_info().sectorbytes != 512) { + logerror("expected 512 bytes per sector, got %d", m_geom[Index]->sectorbytes); + return image_error::INVALIDLENGTH; + } + + m_geom[Index] = &m_hdd[Index]->get_info(); + return std::error_condition(); +} + +void altos586_hdc_device::device_add_mconfig(machine_config &config) +{ + I8089(config, m_iop, XTAL(15'000'000) / 3); + m_iop->set_addrmap(AS_PROGRAM, &altos586_hdc_device::altos586_hdc_mem); + m_iop->set_addrmap(AS_IO, &altos586_hdc_device::altos586_hdc_io); + m_iop->set_data_width(16); + + harddisk_image_device &hdd0(HARDDISK(config, "hdd0", 0)); + hdd0.set_device_load(FUNC(altos586_hdc_device::hdd_load<0U>)); + hdd0.set_device_unload(FUNC(altos586_hdc_device::hdd_unload<0U>)); + + harddisk_image_device &hdd1(HARDDISK(config, "hdd1", 0)); + hdd1.set_device_load(FUNC(altos586_hdc_device::hdd_load<1U>)); + hdd1.set_device_unload(FUNC(altos586_hdc_device::hdd_unload<1U>)); +} + +void altos586_hdc_device::device_reset() +{ + m_status = 0; + m_seek_status = 0xfc; + + m_cyl_latch = 0; + m_cyl[m_drive] = 0; + + memset(m_sector, 0, sizeof(m_sector)); + m_secoffset = 0; +} + +void altos586_hdc_device::attn_w(uint16_t data) +{ + m_iop->ca_w(ASSERT_LINE); + m_iop->ca_w(CLEAR_LINE); +} + +void altos586_hdc_device::device_start() +{ + save_item(NAME(m_status)); + save_item(NAME(m_seek_status)); + save_item(NAME(m_cyl_latch)); + save_item(NAME(m_cyl[0])); + save_item(NAME(m_cyl[1])); + save_pointer(NAME(m_sector), std::size(m_sector)); + save_item(NAME(m_secoffset)); + save_item(NAME(m_drive)); + save_item(NAME(m_head)); +} diff --git a/src/mame/altos/altos586_hdc.h b/src/mame/altos/altos586_hdc.h new file mode 100644 index 00000000000..a16a4fc8fff --- /dev/null +++ b/src/mame/altos/altos586_hdc.h @@ -0,0 +1,85 @@ +// license:BSD-2-Clause +// copyright-holders:Lubomir Rintel + +/*************************************************************************** + + Altos 586 Hard Disk Controller emulation + +***************************************************************************/ + +#ifndef MAME_ALTOS_ALTOS586_HDC_H +#define MAME_ALTOS_ALTOS586_HDC_H + +#pragma once + +#include "cpu/i8089/i8089.h" +#include "imagedev/harddriv.h" + +class altos586_hdc_device : public device_t +{ +public: + template + altos586_hdc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, T &&bus_tag) + : altos586_hdc_device(mconfig, tag, owner, clock) + { + m_bus.set_tag(std::forward(bus_tag), AS_PROGRAM); + } + + altos586_hdc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + + uint16_t mem_r(offs_t offset, uint16_t mem_mask = ~0); + void mem_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0); + + // Register on main bus. + void attn_w(uint16_t data); + +protected: + virtual void device_start() override; + virtual void device_reset() override; + virtual void device_add_mconfig(machine_config &config) override; + + void altos586_hdc_io(address_map &map); + void altos586_hdc_mem(address_map &map); + +private: + // Disk controller registers on IOP's I/O bus. + uint16_t data_r(offs_t offset) ; + void data_w(offs_t offset, uint16_t data); + void head_select_w(offs_t offset, uint16_t data); + uint16_t seek_status_r(offs_t offset) ; + void cyl_w(offs_t offset, uint16_t data); + uint16_t status_r(offs_t offset) ; + void command_w(offs_t offset, uint16_t data); + + // Disk access routines and state. + bool sector_exists(uint8_t index); + uint32_t sector_lba(uint8_t index); + void sector_read(uint8_t index); + void sector_write(uint8_t index); + + // Image mount and unmount. + template std::error_condition hdd_load(device_image_interface &image); + template void hdd_unload(device_image_interface &image) { m_geom[Index] = nullptr; } + + required_address_space m_bus; + required_device m_iop; + required_device_array m_hdd; + + // Disk controller state. + uint8_t m_status; + uint8_t m_seek_status; + + uint16_t m_cyl_latch; + uint16_t m_cyl[2]; + + uint8_t m_sector[517]; + int m_secoffset; + + uint8_t m_drive; + uint8_t m_head; + const hard_disk_file::info *m_geom[2]; +}; + +DECLARE_DEVICE_TYPE(ALTOS586_HDC, altos586_hdc_device) + +#endif // MAME_ALTOS_ALTOS586_HDC_H diff --git a/src/mame/mame.lst b/src/mame/mame.lst index 951cc1f279c..87e540f097e 100644 --- a/src/mame/mame.lst +++ b/src/mame/mame.lst @@ -581,6 +581,9 @@ altos5 // @source:altos/altos486.cpp altos486 +@source:altos/altos586.cpp +altos586 + @source:altos/altos8600.cpp altos8600