altos/altos586.cpp: Added preliminary Altos ACS586 emulation. (#11670)

* cpu/i86: Make "out dx,al" output masked AX on data bus.
* altos/altos586_hdc.cpp: Added Altos 586 Hard Disk Controller emulation.

New systems marked not working
-----------------------
Altos Computer Systems ACS586

New working software list items (altos586)
----------------------------------
Altos Diagnostic Executive
This commit is contained in:
Lubomir Rintel 2024-08-02 20:53:30 +02:00 committed by GitHub
parent cc6a710880
commit 58f600ffe6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1274 additions and 3 deletions

19
hash/altos586.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<!DOCTYPE softwarelist SYSTEM "softwarelist.dtd">
<!--
license:CC0-1.0
-->
<softwarelist name="altos586" description="Altos 586 floppies">
<software name="diag" supported="yes">
<description>Altos Diagnostic Executive</description>
<year>1983</year>
<publisher>Altos Computer Systems</publisher>
<part name="flop1" interface="floppy_5_25">
<dataarea name="flop" size="733392">
<rom name="SDX-586.IMD" size="733392" crc="160fac51" sha1="91a53c8b26bbe4b1f38b1b3811d3176442955d09"/>
</dataarea>
</part>
</software>
</softwarelist>

View File

@ -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;

View File

@ -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;

View File

@ -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

774
src/mame/altos/altos586.cpp Normal file
View File

@ -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<void ()>;
altos586_mmu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
template <typename T>
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<T>(board_tag));
}
template <typename... T> void set_violation_callback(T &&... args) { m_violation_callback.set(std::forward<T>(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<device_memory_interface> 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<i8086_cpu_device> m_cpu;
required_device<altos586_mmu_device> m_mmu;
required_device<ram_device> m_ram;
required_device<pic8259_device> m_pic;
required_device<pit8254_device> m_pit;
required_device<z80_device> m_iop;
required_device<fd1797_device> m_fdc;
required_device_array<floppy_connector, 2> 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)

View File

@ -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 <uint8_t Index>
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));
}

View File

@ -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 <typename T>
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<T>(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 <uint8_t Index> std::error_condition hdd_load(device_image_interface &image);
template <uint8_t Index> void hdd_unload(device_image_interface &image) { m_geom[Index] = nullptr; }
required_address_space m_bus;
required_device<i8089_device> m_iop;
required_device_array<harddisk_image_device, 2> 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

View File

@ -581,6 +581,9 @@ altos5 //
@source:altos/altos486.cpp
altos486
@source:altos/altos586.cpp
altos586
@source:altos/altos8600.cpp
altos8600