mirror of
https://github.com/holub/mame
synced 2025-04-16 21:44:32 +03:00
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:
parent
cc6a710880
commit
58f600ffe6
19
hash/altos586.xml
Normal file
19
hash/altos586.xml
Normal 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>
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
774
src/mame/altos/altos586.cpp
Normal 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)
|
381
src/mame/altos/altos586_hdc.cpp
Normal file
381
src/mame/altos/altos586_hdc.cpp
Normal 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));
|
||||
}
|
85
src/mame/altos/altos586_hdc.h
Normal file
85
src/mame/altos/altos586_hdc.h
Normal 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
|
@ -581,6 +581,9 @@ altos5 //
|
||||
@source:altos/altos486.cpp
|
||||
altos486
|
||||
|
||||
@source:altos/altos586.cpp
|
||||
altos586
|
||||
|
||||
@source:altos/altos8600.cpp
|
||||
altos8600
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user