summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/machine/macscsi.cpp
blob: 7a06b246ad2a47cfb88161c51d737539154cdde1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
// license:BSD-3-Clause
// copyright-holders:AJR
/**********************************************************************

    Apple Macintosh 5380 SCSI helper

    The Macintosh Toolbox's SCSI Manager defines two methods each for
    reading and writing SCSI data (including data comparisons). All
    of these methods are implemented using the 5380's pseudo-DMA mode.
    For SCSIRead and SCSIWrite, the CPU polls DRQ before it reads or
    writes each byte, but SCSIRBlind and SCSIWBlind, in order to
    transfer data as quickly as possible, do not perform this check in
    software except before the very first byte (to give SCSI devices
    time to react to commands). Rather, the hardware enables DRQ onto
    DTACK (MC68000) or DSACK0 (MC68020/030) so wait states can be
    inserted to correspond to SCSI delays. Too long a delay will
    result in a BERR timeout, and the SCSI Manager anticipates bus
    errors by inserting its own exception handler in the vector table.
    The exact timeout is system-dependent. Later Macs will attempt a
    limited number of recoveries from bus errors during SCSI transfers
    before exiting with the scBusTOErr code; the MC68000's rudimentary
    handling of bus errors does not permit this.

    This causes some conflicts between MAME's line-level emulation of
    the SCSI bus and its current M68000 family core, which has no
    working concept of wait states and tries to execute each read or
    write instruction instantaneously. The problems are least
    pronounced on the Macintosh Plus, which never reads or writes more
    than one SCSI byte at a time in any case. However, the SE and
    later models implement the "blind" transfer modes using unrolled
    loops with MOVEP.L (MC68000) or MOVE.L (MC68020/030) to transfer
    four bytes at a time (after first transfering a single byte if its
    buffer pointer is not word-aligned).

    The workaround this device provides is to mediate pseudo-DMA
    transfers through four-byte FIFOs (implementing these using
    32-bit integers is not a coincidence) and halt the CPU to keep
    the FIFO filled during read operations and emptied during write
    operations. In the case of read operations, the buffer must of
    course be filled before CPU attempts its first data read, which
    could be a MOVEP.L or MOVE.L already. To make read operations a
    little more realistic, the device waits until the CPU polls DRQ as
    active before it begins halting the CPU to fill the FIFO. The CPU
    halt will be released if DRQ is not asserted within a certain
    time.

    It is possible for the current implementation to lose data if
    there are still bytes in the FIFO when the pseudo-DMA operation
    ends. However, this is unlikely to be relevant due to a defect in
    the SCSI Manager which Apple's documentation warns about.

**********************************************************************/

#include "emu.h"
#include "macscsi.h"

#define VERBOSE 0
#include "logmacro.h"

static constexpr u8 BAD_BYTE = 0xbb;
static constexpr u8 READ_ERROR = 1;
static constexpr u8 WRITE_ERROR = 0;

// device type definition
DEFINE_DEVICE_TYPE(MAC_SCSI_HELPER, mac_scsi_helper_device, "scsipdma", "Mac 5380 SCSI helper")

ALLOW_SAVE_TYPE(mac_scsi_helper_device::mode);

mac_scsi_helper_device::mac_scsi_helper_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
	: device_t(mconfig, MAC_SCSI_HELPER, tag, owner, clock)
	, m_scsi_read_callback(*this)
	, m_scsi_write_callback(*this)
	, m_scsi_dma_read_callback(*this)
	, m_scsi_dma_write_callback(*this)
	, m_cpu_halt_callback(*this)
	, m_timeout_error_callback(*this)
	, m_timeout(attotime::from_usec(16))
	, m_pseudo_dma_timer(nullptr)
	, m_mode(mode::NON_DMA)
	, m_read_fifo_bytes(0)
	, m_write_fifo_bytes(0)
	, m_read_fifo_data(0)
	, m_write_fifo_data(0)
{
}

void mac_scsi_helper_device::device_resolve_objects()
{
	m_scsi_read_callback.resolve_safe(BAD_BYTE);
	m_scsi_write_callback.resolve_safe();
	m_scsi_dma_read_callback.resolve_safe(BAD_BYTE);
	m_scsi_dma_write_callback.resolve_safe();
	m_cpu_halt_callback.resolve_safe();
	m_timeout_error_callback.resolve_safe();
}

void mac_scsi_helper_device::device_start()
{
	m_pseudo_dma_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(mac_scsi_helper_device::timer_callback), this));

	save_item(NAME(m_mode));
	save_item(NAME(m_read_fifo_bytes));
	save_item(NAME(m_write_fifo_bytes));
	save_item(NAME(m_read_fifo_data));
	save_item(NAME(m_write_fifo_data));
}

void mac_scsi_helper_device::device_reset()
{
	dma_stop();

	m_cpu_halt_callback(CLEAR_LINE);
	m_pseudo_dma_timer->enable(false);
}

void mac_scsi_helper_device::read_fifo_process()
{
	assert(m_read_fifo_bytes < 4);

	u8 data = m_scsi_dma_read_callback(6);
	++m_read_fifo_bytes;
	LOG("Read byte %02X into FIFO (%d/4 filled)\n", data, m_read_fifo_bytes);
	m_read_fifo_data |= u32(data) << (32 - m_read_fifo_bytes * 8);
	if (m_read_fifo_bytes != 4)
		m_pseudo_dma_timer->adjust(m_timeout);
	else
	{
		m_cpu_halt_callback(CLEAR_LINE);
		m_pseudo_dma_timer->enable(false);
	}
}

void mac_scsi_helper_device::write_fifo_process()
{
	assert(m_write_fifo_bytes != 0);
	--m_write_fifo_bytes;
	u8 data = BIT(m_write_fifo_data, m_write_fifo_bytes * 8, 8);
	LOG("Write byte %02X from FIFO (%d left)\n", data, m_write_fifo_bytes);
	m_scsi_dma_write_callback(0, data);

	if (m_write_fifo_bytes != 0)
		m_pseudo_dma_timer->adjust(m_timeout);
	else
	{
		m_cpu_halt_callback(CLEAR_LINE);
		m_pseudo_dma_timer->enable(false);
	}
}

WRITE_LINE_MEMBER(mac_scsi_helper_device::drq_w)
{
	if (state)
	{
		if (m_mode == mode::READ_DMA && m_read_fifo_bytes < 4)
			read_fifo_process();
		else if ((m_mode == mode::WRITE_DMA || m_mode == mode::BAD_DMA) && m_write_fifo_bytes != 0)
			write_fifo_process();
	}
}

TIMER_CALLBACK_MEMBER(mac_scsi_helper_device::timer_callback)
{
	if (m_mode == mode::WRITE_DMA && m_write_fifo_bytes != 0 && BIT(m_scsi_read_callback(5), 6))
		write_fifo_process();
	else if (m_mode == mode::READ_DMA || m_mode == mode::WRITE_DMA)
	{
		LOG("DMA timed out\n");
		m_cpu_halt_callback(CLEAR_LINE);
	}
	else
		dma_stop();
}

void mac_scsi_helper_device::dma_stop()
{
	if (m_read_fifo_bytes != 0)
		logerror("%s: %d unread byte(s) lost (%08X)\n", machine().describe_context(), m_read_fifo_bytes, m_read_fifo_data);
	if (m_write_fifo_bytes != 0)
		logerror("%s: %d unwritten byte(s) lost (%08X)\n", machine().describe_context(), m_write_fifo_bytes, m_write_fifo_data);

	m_mode = mode::NON_DMA;
	m_read_fifo_bytes = 0;
	m_write_fifo_bytes = 0;
	m_read_fifo_data = 0;
	m_write_fifo_data = 0;
}

u8 mac_scsi_helper_device::read_wrapper(bool pseudo_dma, offs_t offset)
{
	u8 data = BAD_BYTE;
	switch (offset & 7)
	{
	case 2:
		data = m_scsi_read_callback(2);
		if (!machine().side_effects_disabled() && !BIT(data, 1))
			dma_stop();
		break;

	case 5:
		data = m_scsi_read_callback(5);
		if (!machine().side_effects_disabled())
		{
			if (!BIT(m_scsi_read_callback(2), 1))
				dma_stop();
			if (m_mode == mode::READ_WAIT_DRQ && BIT(data, 6))
			{
				m_mode = mode::READ_DMA;
				m_read_fifo_data = u32(m_scsi_dma_read_callback(6)) << 24;
				LOG("%s: Pseudo-DMA read started: first byte = %02X\n", machine().describe_context(), m_read_fifo_data >> 24);
				m_read_fifo_bytes = 1;
				m_pseudo_dma_timer->adjust(m_timeout);
				m_cpu_halt_callback(ASSERT_LINE);
			}
		}
		if ((m_mode == mode::READ_DMA && m_read_fifo_bytes != 0) || (m_mode == mode::WRITE_DMA && m_write_fifo_bytes < 4))
			data |= 0x40;
		break;

	case 6:
		if (!machine().side_effects_disabled() && !BIT(m_scsi_read_callback(2), 1))
			dma_stop();
		if (m_read_fifo_bytes != 0)
		{
			data = BIT(m_read_fifo_data, 24, 8);
			if (!machine().side_effects_disabled())
			{
				--m_read_fifo_bytes;
				LOG("%s: CPU read byte %02X from FIFO (%d left)\n", machine().describe_context(), data, m_read_fifo_bytes);
				m_read_fifo_data <<= 8;
				if (BIT(m_scsi_read_callback(5), 6))
					read_fifo_process();
				else if (!m_pseudo_dma_timer->enabled())
				{
					m_pseudo_dma_timer->adjust(m_timeout);
					m_cpu_halt_callback(ASSERT_LINE);
				}
			}
		}
		else if (m_mode != mode::NON_DMA && pseudo_dma)
		{
			if (m_mode != mode::BAD_DMA)
			{
				logerror("%s: Read underflow on SCSI pseudo-DMA\n", machine().describe_context());
				m_mode = mode::BAD_DMA;
			}
			m_timeout_error_callback(READ_ERROR);
			m_pseudo_dma_timer->enable(false);
		}
		else
			data = pseudo_dma ? m_scsi_dma_read_callback(6) : m_scsi_read_callback(6);
		break;

	default:
		if (!machine().side_effects_disabled() && !BIT(m_scsi_read_callback(2), 1))
			dma_stop();
		data = m_scsi_read_callback(offset & 7);
		break;
	}

	return data;
}

void mac_scsi_helper_device::write_wrapper(bool pseudo_dma, offs_t offset, u8 data)
{
	switch (offset & 7)
	{
	case 0:
		if (m_mode == mode::BAD_DMA && pseudo_dma)
			m_timeout_error_callback(WRITE_ERROR);
		else if (m_mode == mode::WRITE_DMA && (m_write_fifo_bytes != 0 || !BIT(m_scsi_read_callback(5), 6)))
		{
			if (m_write_fifo_bytes < 4)
			{
				m_write_fifo_data = (m_write_fifo_data << 8) | data;
				++m_write_fifo_bytes;
				logerror("%s: CPU writing byte %02X into FIFO (%d/4 filled)\n", machine().describe_context(), data, m_write_fifo_bytes);
				if (!m_pseudo_dma_timer->enabled())
				{
					m_pseudo_dma_timer->adjust(m_timeout);
					m_cpu_halt_callback(ASSERT_LINE);
				}
			}
			else if (pseudo_dma)
			{
				logerror("%s: Write overflow on SCSI pseudo-DMA\n", machine().describe_context());
				m_timeout_error_callback(WRITE_ERROR);
				m_mode = mode::BAD_DMA;
			}
		}
		else if (pseudo_dma)
			m_scsi_dma_write_callback(0, data);
		else
			m_scsi_write_callback(0, data);
		break;

	case 2:
		if (!BIT(data, 1))
			dma_stop();
		m_scsi_write_callback(2, data);
		break;

	case 5:
		if (m_mode == mode::NON_DMA && BIT(m_scsi_read_callback(2), 1))
			m_mode = mode::WRITE_DMA;
		m_scsi_write_callback(5, data);
		break;

	case 6: case 7:
		if (m_mode == mode::NON_DMA && BIT(m_scsi_read_callback(2), 1))
			m_mode = mode::READ_WAIT_DRQ;
		m_scsi_write_callback(offset & 7, data);
		break;

	default:
		m_scsi_write_callback(offset & 7, data);
		break;
	}
}