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