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