1 // license:BSD-3-Clause
2 // copyright-holders:Patrick Mackinlay
3 
4 /*
5  * An emulation of the SEEQ 8003 Ethernet Data Link Controller.
6  *
7  * This implementation uses transmit/receive fifos which hold entire frames,
8  * rather than the 16-byte fifos of the real device to simplify logic. In
9  * hardware, RxTxEOF is effectively the 9th bit of the data bus, however to
10  * simplify emulation is implemented as two separate read/write line handlers
11  * which must be used strictly as follows:
12  *
13  *   - rxeof_r() must be read before fifo_r()
14  *   - txeof_w() must be written after fifo_w()
15  *
16  * Sources:
17  *   - http://www.bitsavers.org/components/seeq/_dataBooks/1985_SEEQ_Data_Book.pdf
18  *
19  * TODO:
20  *   - RxDC (discard) and TxRET (retransmit) logic
21  *   - 80c03 inter-packet gap (undocumented)
22  */
23 
24 #include "emu.h"
25 #include "edlc.h"
26 #include "hashing.h"
27 
28 #define LOG_GENERAL (1U << 0)
29 #define LOG_FRAMES  (1U << 1)
30 #define LOG_FILTER  (1U << 2)
31 
32 //#define VERBOSE (LOG_GENERAL|LOG_FRAMES|LOG_FILTER)
33 #include "logmacro.h"
34 
35 DEFINE_DEVICE_TYPE(SEEQ8003,  seeq8003_device,  "seeq8003",  "SEEQ 8003 EDLC")
36 DEFINE_DEVICE_TYPE(SEEQ80C03, seeq80c03_device, "seeq80c03", "SEEQ 80C03 EDLC")
37 
38 static const u8 ETH_BROADCAST[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
39 
seeq8003_device(machine_config const & mconfig,device_type type,char const * tag,device_t * owner,u32 clock)40 seeq8003_device::seeq8003_device(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock)
41 	: device_t(mconfig, type, tag, owner, clock)
42 	, device_network_interface(mconfig, *this, 10.0f)
43 	, m_out_int(*this)
44 	, m_out_rxrdy(*this)
45 	, m_out_txrdy(*this)
46 {
47 }
48 
seeq8003_device(machine_config const & mconfig,char const * tag,device_t * owner,u32 clock)49 seeq8003_device::seeq8003_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock)
50 	: seeq8003_device(mconfig, SEEQ8003, tag, owner, clock)
51 {
52 }
53 
device_start()54 void seeq8003_device::device_start()
55 {
56 	m_out_int.resolve_safe();
57 	m_out_rxrdy.resolve_safe();
58 	m_out_txrdy.resolve_safe();
59 
60 	save_item(NAME(m_int_state));
61 	save_item(NAME(m_reset_state));
62 	save_item(NAME(m_station_address));
63 	save_item(NAME(m_rx_status));
64 	save_item(NAME(m_tx_status));
65 	save_item(NAME(m_rx_command));
66 	save_item(NAME(m_tx_command));
67 	//save_item(NAME(m_rx_fifo));
68 	//save_item(NAME(m_tx_fifo));
69 
70 	m_tx_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(seeq8003_device::transmit), this));
71 	m_int_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(seeq8003_device::interrupt), this));
72 
73 	m_int_state = 0;
74 	m_reset_state = 1;
75 }
76 
device_reset()77 void seeq8003_device::device_reset()
78 {
79 	m_rx_status = RXS_O;
80 	m_tx_status = TXS_O;
81 	m_rx_command = 0;
82 	m_tx_command = 0;
83 
84 	m_rx_fifo.clear();
85 	m_tx_fifo.clear();
86 	m_out_rxrdy(0);
87 
88 	// TODO: deassert RxDC and TxRET
89 
90 	if (m_dev)
91 		m_out_txrdy(1);
92 
93 	interrupt();
94 }
95 
recv_start_cb(u8 * buf,int length)96 int seeq8003_device::recv_start_cb(u8 *buf, int length)
97 {
98 	// check receiver disabled
99 	if (!m_reset_state || ((m_rx_command & RXC_M) == RXC_M0))
100 		return 0;
101 
102 	if (address_filter(buf))
103 	{
104 		LOG("receiving frame length %d\n", length);
105 		dump_bytes(buf, length);
106 
107 		return receive(buf, length);
108 	}
109 
110 	return 0;
111 }
112 
map(address_map & map)113 void seeq8003_device::map(address_map &map)
114 {
115 	map(0, 5).rw(FUNC(seeq8003_device::unused_r), FUNC(seeq8003_device::station_address_w));
116 	map(6, 6).rw(FUNC(seeq8003_device::rx_status_r), FUNC(seeq8003_device::rx_command_w));
117 	map(7, 7).rw(FUNC(seeq8003_device::tx_status_r), FUNC(seeq8003_device::tx_command_w));
118 }
119 
read(offs_t offset)120 u8 seeq8003_device::read(offs_t offset)
121 {
122 	u8 data = 0xff;
123 
124 	switch (offset)
125 	{
126 	case 6: data = rx_status_r(); break;
127 	case 7: data = tx_status_r(); break;
128 	}
129 
130 	return data;
131 }
132 
write(offs_t offset,u8 data)133 void seeq8003_device::write(offs_t offset, u8 data)
134 {
135 	switch (offset)
136 	{
137 	case 0: case 1: case 2:
138 	case 3: case 4: case 5:
139 		station_address_w(offset, data);
140 		break;
141 	case 6: rx_command_w(data); break;
142 	case 7: tx_command_w(data); break;
143 	}
144 }
145 
reset_w(int state)146 void seeq8003_device::reset_w(int state)
147 {
148 	if (m_reset_state && !state)
149 	{
150 		// enter reset state
151 		m_out_txrdy(0);
152 
153 		// TODO: assert RxDC and TxRET
154 	}
155 	else if (!m_reset_state && state)
156 	{
157 		// leave reset state
158 		device_reset();
159 	}
160 
161 	m_reset_state = state;
162 }
163 
fifo_r()164 u8 seeq8003_device::fifo_r()
165 {
166 	if (!m_reset_state)
167 		return 0xff;
168 
169 	if (m_rx_fifo.empty())
170 		fatalerror("seeq8003_device::fifo_r: fifo empty\n");
171 
172 	u8 const data = m_rx_fifo.dequeue();
173 
174 	if (m_rx_fifo.empty())
175 	{
176 		// disable rx fifo
177 		m_out_rxrdy(0);
178 
179 		// schedule interrupt
180 		m_int_timer->adjust(attotime::zero);
181 	}
182 
183 	return data;
184 }
185 
rxeof_r()186 int seeq8003_device::rxeof_r()
187 {
188 	// HACK: should be asserted while the last byte is being read from the fifo
189 	// but doing it when the fifo is empty makes emulation simpler
190 	return m_rx_fifo.empty();
191 }
192 
fifo_w(u8 data)193 void seeq8003_device::fifo_w(u8 data)
194 {
195 	if (!m_reset_state)
196 		return;
197 
198 	if (m_tx_fifo.full())
199 		fatalerror("seeq8003_device::fifo_w: fifo full\n");
200 
201 	m_tx_fifo.enqueue(data);
202 
203 	if (m_tx_fifo.full())
204 		m_out_txrdy(0);
205 }
206 
txeof_w(int state)207 void seeq8003_device::txeof_w(int state)
208 {
209 	if (m_reset_state && state)
210 	{
211 		// disable tx fifo
212 		m_out_txrdy(0);
213 
214 		// schedule transmit
215 		m_tx_timer->adjust(attotime::zero);
216 	}
217 }
218 
rx_status_r()219 u8 seeq8003_device::rx_status_r()
220 {
221 	u8 const data = m_rx_status;
222 
223 	// clear interrupt
224 	if (m_reset_state && !machine().side_effects_disabled())
225 	{
226 		m_rx_status |= RXS_O;
227 		m_int_timer->adjust(attotime::zero);
228 	}
229 
230 	return data;
231 }
232 
tx_status_r()233 u8 seeq8003_device::tx_status_r()
234 {
235 	u8 const data = m_tx_status;
236 
237 	// clear interrupt
238 	if (m_reset_state && !machine().side_effects_disabled())
239 	{
240 		m_tx_status |= TXS_O;
241 		m_int_timer->adjust(attotime::zero);
242 	}
243 
244 	return data;
245 }
246 
rx_command_w(u8 data)247 void seeq8003_device::rx_command_w(u8 data)
248 {
249 	LOG("rx_command_w 0x%02x (%s)\n", data, machine().describe_context());
250 
251 	m_rx_command = data;
252 }
253 
tx_command_w(u8 data)254 void seeq8003_device::tx_command_w(u8 data)
255 {
256 	LOG("tx_command_w 0x%02x (%s)\n", data, machine().describe_context());
257 
258 	m_tx_command = data;
259 }
260 
transmit(void * ptr,int param)261 void seeq8003_device::transmit(void *ptr, int param)
262 {
263 	if (m_tx_fifo.queue_length())
264 	{
265 		u8 buf[MAX_FRAME_SIZE];
266 		int length = 0;
267 
268 		// dequeue to buffer
269 		while (!m_tx_fifo.empty())
270 			buf[length++] = m_tx_fifo.dequeue();
271 
272 		// transmit packet autopad
273 		if (mode_tx_pad() && (length < 60))
274 			while (length < 60)
275 				buf[length++] = 0;
276 
277 		// compute and append fcs
278 		if (mode_tx_crc())
279 		{
280 			u32 const fcs = util::crc32_creator::simple(buf, length);
281 			buf[length++] = (fcs >> 0) & 0xff;
282 			buf[length++] = (fcs >> 8) & 0xff;
283 			buf[length++] = (fcs >> 16) & 0xff;
284 			buf[length++] = (fcs >> 24) & 0xff;
285 		}
286 
287 		LOG("transmitting frame length %d\n", length);
288 		dump_bytes(buf, length);
289 
290 		// transmit the frame
291 		send(buf, length);
292 
293 		// TODO: transmit errors/TxRET
294 
295 		// update status
296 		m_tx_status = TXS_S;
297 	}
298 	else
299 		m_tx_status = TXS_U;
300 
301 	// enable tx fifo
302 	m_out_txrdy(1);
303 
304 	interrupt();
305 }
306 
receive(u8 * buf,int length)307 int seeq8003_device::receive(u8 *buf, int length)
308 {
309 	// discard if rx status has not been read
310 	// TODO: RxDC
311 	if (!(m_rx_status & RXS_O))
312 		return 0;
313 
314 	m_rx_status = RXS_E;
315 
316 	// check for errors
317 	u32 const fcs = util::crc32_creator::simple(buf, length);
318 	if (length < 64)
319 		m_rx_status |= RXS_S;
320 	else if (~fcs != FCS_RESIDUE)
321 		m_rx_status |= RXS_C;
322 	else
323 		m_rx_status |= RXS_G;
324 
325 	// enqueue from buffer
326 	unsigned const fcs_bytes = mode_rx_crc() ? 0 : 4;
327 	for (unsigned i = 0; i < length - fcs_bytes; i++)
328 		m_rx_fifo.enqueue(buf[i]);
329 
330 	// enable rx fifo
331 	m_out_rxrdy(1);
332 
333 	return length;
334 }
335 
interrupt(void * ptr,int param)336 void seeq8003_device::interrupt(void *ptr, int param)
337 {
338 	int const state =
339 		(!(m_tx_status & TXS_O) && (m_tx_status & m_tx_command & TXS_M)) ||
340 		(!(m_rx_status & RXS_O) && (m_rx_status & m_rx_command & RXS_M));
341 
342 	// TODO: assert RxDC for masked rx crc or short frame errors
343 
344 	if (state != m_int_state)
345 	{
346 		m_int_state = state;
347 		m_out_int(state);
348 	}
349 }
350 
address_filter(u8 * address)351 bool seeq8003_device::address_filter(u8 *address)
352 {
353 	LOGMASKED(LOG_FILTER, "address_filter testing destination address %02x:%02x:%02x:%02x:%02x:%02x\n",
354 		address[0], address[1], address[2], address[3], address[4], address[5]);
355 
356 	if ((m_rx_command & RXC_M) == RXC_M1)
357 	{
358 		LOGMASKED(LOG_FILTER, "address_filter accepted: promiscuous mode enabled\n");
359 
360 		return true;
361 	}
362 
363 	// station address
364 	if (!memcmp(address, m_station_address, 6))
365 	{
366 		LOGMASKED(LOG_FILTER, "address_filter accepted: station address match\n");
367 
368 		return true;
369 	}
370 
371 	// ethernet broadcast
372 	if (!memcmp(address, ETH_BROADCAST, 6))
373 	{
374 		LOGMASKED(LOG_FILTER, "address_filter accepted: broadcast\n");
375 
376 		return true;
377 	}
378 
379 	// ethernet multicast
380 	if (((m_rx_command & RXC_M) == RXC_M3) && (address[0] & 0x1))
381 	{
382 		LOGMASKED(LOG_FILTER, "address_filter accepted: multicast address match\n");
383 
384 		return true;
385 	}
386 
387 	return false;
388 }
389 
seeq80c03_device(machine_config const & mconfig,char const * tag,device_t * owner,u32 clock)390 seeq80c03_device::seeq80c03_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock)
391 	: seeq8003_device(mconfig, SEEQ80C03, tag, owner, clock)
392 	, m_regbank(*this, "regbank")
393 {
394 }
395 
device_add_mconfig(machine_config & config)396 void seeq80c03_device::device_add_mconfig(machine_config &config)
397 {
398 	ADDRESS_MAP_BANK(config, m_regbank).set_map(&seeq80c03_device::map_reg).set_options(ENDIANNESS_NATIVE, 8, 5, 8);
399 }
400 
device_start()401 void seeq80c03_device::device_start()
402 {
403 	seeq8003_device::device_start();
404 
405 	save_item(NAME(m_tx_cc));
406 	save_item(NAME(m_cc));
407 	save_item(NAME(m_flags));
408 	save_item(NAME(m_control));
409 	save_item(NAME(m_config));
410 	save_item(NAME(m_multicast_filter));
411 }
412 
device_reset()413 void seeq80c03_device::device_reset()
414 {
415 	seeq8003_device::device_reset();
416 
417 	m_tx_cc = 0;
418 	m_cc = 0;
419 	m_flags = 0;
420 	m_control = 0;
421 	m_config = 0;
422 	m_multicast_filter = 0;
423 }
424 
send_complete_cb(int result)425 void seeq80c03_device::send_complete_cb(int result)
426 {
427 	if (result)
428 	{
429 		if (m_control & CTL_SQE)
430 			m_flags |= FLAGS_SQE;
431 	}
432 	else
433 	{
434 		// assume transmit failure and no device means loss of carrier
435 		if ((m_control & CTL_TNC) && !m_dev)
436 			m_flags |= FLAGS_TNC;
437 	}
438 }
439 
map(address_map & map)440 void seeq80c03_device::map(address_map &map)
441 {
442 	map(0, 7).m(m_regbank, FUNC(address_map_bank_device::amap8));
443 }
444 
map_reg(address_map & map)445 void seeq80c03_device::map_reg(address_map &map)
446 {
447 	map(0x00, 0x05).w(FUNC(seeq80c03_device::station_address_w));
448 
449 	map(0x08, 0x11).w(FUNC(seeq80c03_device::multicast_filter_w));
450 	map(0x12, 0x12).nopw(); // TODO: inter-packet gap
451 	map(0x13, 0x13).w(FUNC(seeq80c03_device::control_w));
452 	map(0x14, 0x14).w(FUNC(seeq80c03_device::config_w));
453 
454 	map(0x00, 0x00).r(FUNC(seeq80c03_device::tx_ccl_r)).mirror(0x18);
455 	map(0x01, 0x01).r(FUNC(seeq80c03_device::tx_cch_r)).mirror(0x18);
456 	map(0x02, 0x02).r(FUNC(seeq80c03_device::ccl_r)).mirror(0x18);
457 	map(0x03, 0x03).r(FUNC(seeq80c03_device::cch_r)).mirror(0x18);
458 	map(0x04, 0x04).r(FUNC(seeq80c03_device::test_r)).mirror(0x18);
459 	map(0x05, 0x05).r(FUNC(seeq80c03_device::flags_r)).mirror(0x18);
460 
461 	map(0x06, 0x06).rw(FUNC(seeq80c03_device::rx_status_r), FUNC(seeq80c03_device::rx_command_w)).mirror(0x18);
462 	map(0x07, 0x07).rw(FUNC(seeq80c03_device::tx_status_r), FUNC(seeq80c03_device::tx_command_w)).mirror(0x18);
463 }
464 
read(offs_t offset)465 u8 seeq80c03_device::read(offs_t offset)
466 {
467 	u8 data = 0xff;
468 
469 	switch (offset)
470 	{
471 	case 0: data = tx_ccl_r(); break;
472 	case 1: data = tx_cch_r(); break;
473 	case 2: data = ccl_r(); break;
474 	case 3: data = cch_r(); break;
475 	case 4: data = test_r(); break;
476 	case 5: data = flags_r(); break;
477 	case 6: data = rx_status_r(); break;
478 	case 7: data = tx_status_r(); break;
479 	}
480 
481 	return data;
482 }
483 
write(offs_t offset,u8 data)484 void seeq80c03_device::write(offs_t offset, u8 data)
485 {
486 	switch (m_tx_command & TXC_B)
487 	{
488 	case 0x00:
489 		switch (offset)
490 		{
491 		case 0: case 1: case 2:
492 		case 3: case 4: case 5:
493 			station_address_w(offset, data);
494 			break;
495 		case 6: rx_command_w(data); break;
496 		case 7: tx_command_w(data); break;
497 		}
498 		break;
499 	case 0x20:
500 		switch (offset)
501 		{
502 		case 0: case 1: case 2:
503 		case 3: case 4: case 5:
504 			multicast_filter_w(offset, data);
505 			break;
506 		case 6: rx_command_w(data); break;
507 		case 7: tx_command_w(data); break;
508 		}
509 		break;
510 	case 0x40:
511 		switch (offset)
512 		{
513 		case 0: case 1:
514 			multicast_filter_w(offset + 8, data);
515 			break;
516 		case 2: break; // TODO: inter-packet gap
517 		case 3: control_w(data); break;
518 		case 4: config_w(data); break;
519 		case 6: rx_command_w(data); break;
520 		case 7: tx_command_w(data); break;
521 		}
522 		break;
523 	case 0x60:
524 		switch (offset)
525 		{
526 		case 6: rx_command_w(data); break;
527 		case 7: tx_command_w(data); break;
528 		}
529 		break;
530 	}
531 }
532 
multicast_filter_w(offs_t offset,u8 data)533 void seeq80c03_device::multicast_filter_w(offs_t offset, u8 data)
534 {
535 	unsigned const shift = (offset < 6) ? (offset * 8) : ((offset - 2) * 8);
536 
537 	m_multicast_filter &= ~(u64(0xff) << shift);
538 	m_multicast_filter |= u64(data) << shift;
539 }
540 
tx_command_w(u8 data)541 void seeq80c03_device::tx_command_w(u8 data)
542 {
543 	seeq8003_device::tx_command_w(data);
544 
545 	m_regbank->set_bank((data >> 5) & 3);
546 }
547 
control_w(u8 data)548 void seeq80c03_device::control_w(u8 data)
549 {
550 	LOG("control_w 0x%02x (%s)\n", data, machine().describe_context());
551 
552 	if ((m_control & CTL_TCC) && !(data & CTL_TCC))
553 		m_tx_cc = 0;
554 	if ((m_control & CTL_CC) && !(data & CTL_CC))
555 		m_cc = 0;
556 	if ((m_control & CTL_SQE) && !(data & CTL_SQE))
557 		m_flags &= ~FLAGS_SQE;
558 	if ((m_control & CTL_TNC) && !(data & CTL_TNC))
559 		m_flags &= ~FLAGS_TNC;
560 
561 	m_control = data;
562 }
563 
config_w(u8 data)564 void seeq80c03_device::config_w(u8 data)
565 {
566 	LOG("config_w 0x%02x (%s)\n", data, machine().describe_context());
567 
568 	m_config = data;
569 }
570 
address_filter(u8 * address)571 bool seeq80c03_device::address_filter(u8 *address)
572 {
573 	LOGMASKED(LOG_FILTER, "address_filter testing destination address %02x:%02x:%02x:%02x:%02x:%02x\n",
574 		address[0], address[1], address[2], address[3], address[4], address[5]);
575 
576 	if ((m_rx_command & RXC_M) == RXC_M1)
577 	{
578 		LOGMASKED(LOG_FILTER, "address_filter accepted: promiscuous mode enabled\n");
579 
580 		return true;
581 	}
582 
583 	// station address
584 	if (m_config & CFG_GAM)
585 	{
586 		if (!memcmp(address, m_station_address, 5) && !(((address[5] ^ m_station_address[5]) & 0xf0)))
587 		{
588 			LOGMASKED(LOG_FILTER, "address_filter accepted: station address match\n");
589 
590 			return true;
591 		}
592 	}
593 	else if (!memcmp(address, m_station_address, 6))
594 	{
595 		LOGMASKED(LOG_FILTER, "address_filter accepted: station address match\n");
596 
597 		return true;
598 	}
599 
600 	// ethernet broadcast
601 	if (!memcmp(address, ETH_BROADCAST, 6))
602 	{
603 		LOGMASKED(LOG_FILTER, "address_filter accepted: broadcast\n");
604 
605 		return true;
606 	}
607 
608 	// ethernet multicast
609 	if (((m_rx_command & RXC_M) == RXC_M3) && (address[0] & 0x1))
610 	{
611 		// multicast hash filter
612 		if (m_control & CTL_MHF)
613 		{
614 			u32 const crc = util::crc32_creator::simple(address, 6);
615 
616 			if (!BIT(m_multicast_filter, crc & 63))
617 				return false;
618 		}
619 
620 		LOGMASKED(LOG_FILTER, "address_filter accepted: multicast address match\n");
621 
622 		return true;
623 	}
624 
625 	return false;
626 }
627 
dump_bytes(u8 * buf,int length)628 void seeq8003_device::dump_bytes(u8 *buf, int length)
629 {
630 	if (VERBOSE & LOG_FRAMES)
631 	{
632 		// pad frame with zeros to 8-byte boundary
633 		for (int i = 0; i < 8 - (length % 8); i++)
634 			buf[length + i] = 0;
635 
636 		// dump length / 8 (rounded up) groups of 8 bytes
637 		for (int i = 0; i < (length + 7) / 8; i++)
638 			LOGMASKED(LOG_FRAMES, "%02x %02x %02x %02x %02x %02x %02x %02x\n",
639 				buf[i * 8 + 0], buf[i * 8 + 1], buf[i * 8 + 2], buf[i * 8 + 3],
640 				buf[i * 8 + 4], buf[i * 8 + 5], buf[i * 8 + 6], buf[i * 8 + 7]);
641 	}
642 }
643