1 // Copyright 2008 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4
5 #include "Core/HW/EXI/EXI_DeviceEthernet.h"
6
7 #include <algorithm>
8 #include <memory>
9 #include <optional>
10 #include <string>
11
12 #include "Common/ChunkFile.h"
13 #include "Common/CommonTypes.h"
14 #include "Common/Logging/Log.h"
15 #include "Common/Network.h"
16 #include "Common/StringUtil.h"
17 #include "Core/ConfigManager.h"
18 #include "Core/CoreTiming.h"
19 #include "Core/HW/EXI/EXI.h"
20 #include "Core/HW/Memmap.h"
21
22 namespace ExpansionInterface
23 {
24 // XXX: The BBA stores multi-byte elements as little endian.
25 // Multiple parts of this implementation depend on Dolphin
26 // being compiled for a little endian host.
27
CEXIETHERNET(BBADeviceType type)28 CEXIETHERNET::CEXIETHERNET(BBADeviceType type)
29 {
30 // Parse MAC address from config, and generate a new one if it doesn't
31 // exist or can't be parsed.
32 std::string& mac_addr_setting = SConfig::GetInstance().m_bba_mac;
33 std::optional<Common::MACAddress> mac_addr = Common::StringToMacAddress(mac_addr_setting);
34
35 std::transform(mac_addr_setting.begin(), mac_addr_setting.end(), mac_addr_setting.begin(),
36 [](unsigned char c) { return std::tolower(c); });
37
38 if (!mac_addr)
39 {
40 mac_addr = Common::GenerateMacAddress(Common::MACConsumer::BBA);
41 mac_addr_setting = Common::MacAddressToString(mac_addr.value());
42 SConfig::GetInstance().SaveSettings();
43 }
44
45 switch (type)
46 {
47 case BBADeviceType::TAP:
48 m_network_interface = std::make_unique<TAPNetworkInterface>(this);
49 INFO_LOG(SP1, "Created TAP physical network interface.");
50 break;
51 case BBADeviceType::XLINK:
52 // TODO start BBA with network link down, bring it up after "connected" response from XLink
53
54 // Perform sanity check on BBA MAC address, XLink requires the vendor OUI to be Nintendo's and
55 // to be one of the two used for the GameCube.
56 // Don't actually stop the BBA from initializing though
57 if (!StringBeginsWith(mac_addr_setting, "00:09:bf") &&
58 !StringBeginsWith(mac_addr_setting, "00:17:ab"))
59 {
60 PanicAlertT("BBA MAC address %s invalid for XLink Kai. A valid Nintendo GameCube MAC address "
61 "must be used. Generate a new MAC address starting with 00:09:bf or 00:17:ab.",
62 mac_addr_setting.c_str());
63 }
64
65 // m_client_mdentifier should be unique per connected emulator from the XLink kai client's
66 // perspective so lets use "dolphin<bba mac>"
67 m_network_interface = std::make_unique<XLinkNetworkInterface>(
68 this, SConfig::GetInstance().m_bba_xlink_ip, 34523,
69 "dolphin" + SConfig::GetInstance().m_bba_mac, SConfig::GetInstance().m_bba_xlink_chat_osd);
70 INFO_LOG(SP1, "Created XLink Kai BBA network interface connection to %s:34523",
71 SConfig::GetInstance().m_bba_xlink_ip.c_str());
72 break;
73 }
74
75 tx_fifo = std::make_unique<u8[]>(BBA_TXFIFO_SIZE);
76 mBbaMem = std::make_unique<u8[]>(BBA_MEM_SIZE);
77 mRecvBuffer = std::make_unique<u8[]>(BBA_RECV_SIZE);
78
79 MXHardReset();
80
81 const auto& mac = mac_addr.value();
82 memcpy(&mBbaMem[BBA_NAFR_PAR0], mac.data(), mac.size());
83
84 // HACK: .. fully established 100BASE-T link
85 mBbaMem[BBA_NWAYS] = NWAYS_LS100 | NWAYS_LPNWAY | NWAYS_100TXF | NWAYS_ANCLPT;
86 }
87
~CEXIETHERNET()88 CEXIETHERNET::~CEXIETHERNET()
89 {
90 m_network_interface->Deactivate();
91 }
92
SetCS(int cs)93 void CEXIETHERNET::SetCS(int cs)
94 {
95 if (cs)
96 {
97 // Invalidate the previous transfer
98 transfer.valid = false;
99 }
100 }
101
IsPresent() const102 bool CEXIETHERNET::IsPresent() const
103 {
104 return true;
105 }
106
IsInterruptSet()107 bool CEXIETHERNET::IsInterruptSet()
108 {
109 return !!(exi_status.interrupt & exi_status.interrupt_mask);
110 }
111
ImmWrite(u32 data,u32 size)112 void CEXIETHERNET::ImmWrite(u32 data, u32 size)
113 {
114 data >>= (4 - size) * 8;
115
116 if (!transfer.valid)
117 {
118 transfer.valid = true;
119 transfer.region = IsMXCommand(data) ? transfer.MX : transfer.EXI;
120 if (transfer.region == transfer.EXI)
121 transfer.address = ((data & ~0xc000) >> 8) & 0xff;
122 else
123 transfer.address = (data >> 8) & 0xffff;
124 transfer.direction = IsWriteCommand(data) ? transfer.WRITE : transfer.READ;
125
126 DEBUG_LOG(SP1, "%s %s %s %x", IsMXCommand(data) ? "mx " : "exi",
127 IsWriteCommand(data) ? "write" : "read ", GetRegisterName(), transfer.address);
128
129 if (transfer.address == BBA_IOB && transfer.region == transfer.MX)
130 {
131 ERROR_LOG(SP1, "Usage of BBA_IOB indicates that the rx packet descriptor has been corrupted. "
132 "Killing Dolphin...");
133 exit(0);
134 }
135
136 // transfer has been setup
137 return;
138 }
139
140 // Reach here if we're actually writing data to the EXI or MX region.
141
142 DEBUG_LOG(SP1, "%s write %0*x", transfer.region == transfer.MX ? "mx " : "exi", size * 2, data);
143
144 if (transfer.region == transfer.EXI)
145 {
146 switch (transfer.address)
147 {
148 case INTERRUPT:
149 exi_status.interrupt &= data ^ 0xff;
150 break;
151 case INTERRUPT_MASK:
152 exi_status.interrupt_mask = data;
153 break;
154 }
155 ExpansionInterface::UpdateInterrupts();
156 }
157 else
158 {
159 MXCommandHandler(data, size);
160 }
161 }
162
ImmRead(u32 size)163 u32 CEXIETHERNET::ImmRead(u32 size)
164 {
165 u32 ret = 0;
166
167 if (transfer.region == transfer.EXI)
168 {
169 switch (transfer.address)
170 {
171 case EXI_ID:
172 ret = EXI_DEVTYPE_ETHER;
173 break;
174 case REVISION_ID:
175 ret = exi_status.revision_id;
176 break;
177 case DEVICE_ID:
178 ret = exi_status.device_id;
179 break;
180 case ACSTART:
181 ret = exi_status.acstart;
182 break;
183 case INTERRUPT:
184 ret = exi_status.interrupt;
185 break;
186 }
187
188 transfer.address += size;
189 }
190 else
191 {
192 for (int i = size - 1; i >= 0; i--)
193 ret |= mBbaMem[transfer.address++] << (i * 8);
194 }
195
196 DEBUG_LOG(SP1, "imm r%i: %0*x", size, size * 2, ret);
197
198 ret <<= (4 - size) * 8;
199
200 return ret;
201 }
202
DMAWrite(u32 addr,u32 size)203 void CEXIETHERNET::DMAWrite(u32 addr, u32 size)
204 {
205 DEBUG_LOG(SP1, "DMA write: %08x %x", addr, size);
206
207 if (transfer.region == transfer.MX && transfer.direction == transfer.WRITE &&
208 transfer.address == BBA_WRTXFIFOD)
209 {
210 DirectFIFOWrite(Memory::GetPointer(addr), size);
211 }
212 else
213 {
214 ERROR_LOG(SP1, "DMA write in %s %s mode - not implemented",
215 transfer.region == transfer.EXI ? "exi" : "mx",
216 transfer.direction == transfer.READ ? "read" : "write");
217 }
218 }
219
DMARead(u32 addr,u32 size)220 void CEXIETHERNET::DMARead(u32 addr, u32 size)
221 {
222 DEBUG_LOG(SP1, "DMA read: %08x %x", addr, size);
223
224 Memory::CopyToEmu(addr, &mBbaMem[transfer.address], size);
225
226 transfer.address += size;
227 }
228
DoState(PointerWrap & p)229 void CEXIETHERNET::DoState(PointerWrap& p)
230 {
231 p.DoArray(tx_fifo.get(), BBA_TXFIFO_SIZE);
232 p.DoArray(mBbaMem.get(), BBA_MEM_SIZE);
233 }
234
IsMXCommand(u32 const data)235 bool CEXIETHERNET::IsMXCommand(u32 const data)
236 {
237 return !!(data & (1 << 31));
238 }
239
IsWriteCommand(u32 const data)240 bool CEXIETHERNET::IsWriteCommand(u32 const data)
241 {
242 return IsMXCommand(data) ? !!(data & (1 << 30)) : !!(data & (1 << 14));
243 }
244
GetRegisterName() const245 const char* CEXIETHERNET::GetRegisterName() const
246 {
247 #define STR_RETURN(x) \
248 case x: \
249 return #x;
250
251 if (transfer.region == transfer.EXI)
252 {
253 switch (transfer.address)
254 {
255 STR_RETURN(EXI_ID)
256 STR_RETURN(REVISION_ID)
257 STR_RETURN(INTERRUPT)
258 STR_RETURN(INTERRUPT_MASK)
259 STR_RETURN(DEVICE_ID)
260 STR_RETURN(ACSTART)
261 STR_RETURN(HASH_READ)
262 STR_RETURN(HASH_WRITE)
263 STR_RETURN(HASH_STATUS)
264 STR_RETURN(RESET)
265 default:
266 return "unknown";
267 }
268 }
269 else
270 {
271 switch (transfer.address)
272 {
273 STR_RETURN(BBA_NCRA)
274 STR_RETURN(BBA_NCRB)
275 STR_RETURN(BBA_LTPS)
276 STR_RETURN(BBA_LRPS)
277 STR_RETURN(BBA_IMR)
278 STR_RETURN(BBA_IR)
279 STR_RETURN(BBA_BP)
280 STR_RETURN(BBA_TLBP)
281 STR_RETURN(BBA_TWP)
282 STR_RETURN(BBA_IOB)
283 STR_RETURN(BBA_TRP)
284 STR_RETURN(BBA_RWP)
285 STR_RETURN(BBA_RRP)
286 STR_RETURN(BBA_RHBP)
287 STR_RETURN(BBA_RXINTT)
288 STR_RETURN(BBA_NAFR_PAR0)
289 STR_RETURN(BBA_NAFR_PAR1)
290 STR_RETURN(BBA_NAFR_PAR2)
291 STR_RETURN(BBA_NAFR_PAR3)
292 STR_RETURN(BBA_NAFR_PAR4)
293 STR_RETURN(BBA_NAFR_PAR5)
294 STR_RETURN(BBA_NAFR_MAR0)
295 STR_RETURN(BBA_NAFR_MAR1)
296 STR_RETURN(BBA_NAFR_MAR2)
297 STR_RETURN(BBA_NAFR_MAR3)
298 STR_RETURN(BBA_NAFR_MAR4)
299 STR_RETURN(BBA_NAFR_MAR5)
300 STR_RETURN(BBA_NAFR_MAR6)
301 STR_RETURN(BBA_NAFR_MAR7)
302 STR_RETURN(BBA_NWAYC)
303 STR_RETURN(BBA_NWAYS)
304 STR_RETURN(BBA_GCA)
305 STR_RETURN(BBA_MISC)
306 STR_RETURN(BBA_TXFIFOCNT)
307 STR_RETURN(BBA_WRTXFIFOD)
308 STR_RETURN(BBA_MISC2)
309 STR_RETURN(BBA_SI_ACTRL)
310 STR_RETURN(BBA_SI_STATUS)
311 STR_RETURN(BBA_SI_ACTRL2)
312 default:
313 if (transfer.address >= 0x100 && transfer.address <= 0xfff)
314 return "packet buffer";
315 else
316 return "unknown";
317 }
318 }
319
320 #undef STR_RETURN
321 }
322
MXHardReset()323 void CEXIETHERNET::MXHardReset()
324 {
325 memset(mBbaMem.get(), 0, BBA_MEM_SIZE);
326
327 mBbaMem[BBA_NCRB] = NCRB_PR;
328 mBbaMem[BBA_NWAYC] = NWAYC_LTE | NWAYC_ANE;
329 mBbaMem[BBA_MISC] = MISC1_TPF | MISC1_TPH | MISC1_TXF | MISC1_TXH;
330 }
331
MXCommandHandler(u32 data,u32 size)332 void CEXIETHERNET::MXCommandHandler(u32 data, u32 size)
333 {
334 switch (transfer.address)
335 {
336 case BBA_NCRA:
337 if (data & NCRA_RESET)
338 {
339 INFO_LOG(SP1, "Software reset");
340 // MXSoftReset();
341 m_network_interface->Activate();
342 }
343
344 if ((mBbaMem[BBA_NCRA] & NCRA_SR) ^ (data & NCRA_SR))
345 {
346 DEBUG_LOG(SP1, "%s rx", (data & NCRA_SR) ? "start" : "stop");
347
348 if (data & NCRA_SR)
349 m_network_interface->RecvStart();
350 else
351 m_network_interface->RecvStop();
352 }
353
354 // Only start transfer if there isn't one currently running
355 if (!(mBbaMem[BBA_NCRA] & (NCRA_ST0 | NCRA_ST1)))
356 {
357 // Technically transfer DMA status is kept in TXDMA - not implemented
358
359 if (data & NCRA_ST0)
360 {
361 INFO_LOG(SP1, "start tx - local DMA");
362 SendFromPacketBuffer();
363 }
364 else if (data & NCRA_ST1)
365 {
366 DEBUG_LOG(SP1, "start tx - direct FIFO");
367 SendFromDirectFIFO();
368 // Kind of a hack: send completes instantly, so we don't
369 // actually write the "send in status" bit to the register
370 data &= ~NCRA_ST1;
371 }
372 }
373 goto write_to_register;
374
375 case BBA_WRTXFIFOD:
376 if (size == 2)
377 data = Common::swap16(data & 0xffff);
378 else if (size == 3)
379 data = Common::swap32(data & 0xffffff) >> 8;
380 else if (size == 4)
381 data = Common::swap32(data);
382 DirectFIFOWrite((u8*)&data, size);
383 // Do not increment address
384 return;
385
386 case BBA_IR:
387 data &= (data & 0xff) ^ 0xff;
388 goto write_to_register;
389
390 case BBA_TXFIFOCNT:
391 case BBA_TXFIFOCNT + 1:
392 // Ignore all writes to BBA_TXFIFOCNT
393 transfer.address += size;
394 return;
395
396 write_to_register:
397 default:
398 for (int i = size - 1; i >= 0; i--)
399 {
400 mBbaMem[transfer.address++] = (data >> (i * 8)) & 0xff;
401 }
402 return;
403 }
404 }
405
DirectFIFOWrite(const u8 * data,u32 size)406 void CEXIETHERNET::DirectFIFOWrite(const u8* data, u32 size)
407 {
408 // In direct mode, the hardware handles creating the state required by the
409 // GMAC instead of finagling with packet descriptors and such
410
411 u16* tx_fifo_count = (u16*)&mBbaMem[BBA_TXFIFOCNT];
412
413 memcpy(tx_fifo.get() + *tx_fifo_count, data, size);
414
415 *tx_fifo_count += size;
416 // TODO: not sure this mask is correct.
417 // However, BBA_TXFIFOCNT should never get even close to this amount,
418 // so it shouldn't matter
419 *tx_fifo_count &= (1 << 12) - 1;
420 }
421
SendFromDirectFIFO()422 void CEXIETHERNET::SendFromDirectFIFO()
423 {
424 m_network_interface->SendFrame(tx_fifo.get(), *(u16*)&mBbaMem[BBA_TXFIFOCNT]);
425 }
426
SendFromPacketBuffer()427 void CEXIETHERNET::SendFromPacketBuffer()
428 {
429 ERROR_LOG(SP1, "tx packet buffer not implemented.");
430 }
431
SendComplete()432 void CEXIETHERNET::SendComplete()
433 {
434 mBbaMem[BBA_NCRA] &= ~(NCRA_ST0 | NCRA_ST1);
435 *(u16*)&mBbaMem[BBA_TXFIFOCNT] = 0;
436
437 if (mBbaMem[BBA_IMR] & INT_T)
438 {
439 mBbaMem[BBA_IR] |= INT_T;
440
441 exi_status.interrupt |= exi_status.TRANSFER;
442 ExpansionInterface::ScheduleUpdateInterrupts(CoreTiming::FromThread::CPU, 0);
443 }
444
445 mBbaMem[BBA_LTPS] = 0;
446 }
447
HashIndex(const u8 * dest_eth_addr)448 inline u8 CEXIETHERNET::HashIndex(const u8* dest_eth_addr)
449 {
450 // Calculate CRC
451 u32 crc = 0xffffffff;
452
453 for (size_t byte_num = 0; byte_num < 6; ++byte_num)
454 {
455 u8 cur_byte = dest_eth_addr[byte_num];
456 for (size_t bit = 0; bit < 8; ++bit)
457 {
458 u8 carry = ((crc >> 31) & 1) ^ (cur_byte & 1);
459 crc <<= 1;
460 cur_byte >>= 1;
461 if (carry)
462 crc = (crc ^ 0x4c11db6) | carry;
463 }
464 }
465
466 // return bits used as index
467 return crc >> 26;
468 }
469
RecvMACFilter()470 inline bool CEXIETHERNET::RecvMACFilter()
471 {
472 static u8 const broadcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
473
474 // Accept all destination addrs?
475 if (mBbaMem[BBA_NCRB] & NCRB_PR)
476 return true;
477
478 // Unicast?
479 if ((mRecvBuffer[0] & 0x01) == 0)
480 {
481 return memcmp(mRecvBuffer.get(), &mBbaMem[BBA_NAFR_PAR0], 6) == 0;
482 }
483 else if (memcmp(mRecvBuffer.get(), broadcast, 6) == 0)
484 {
485 // Accept broadcast?
486 return !!(mBbaMem[BBA_NCRB] & NCRB_AB);
487 }
488 else if (mBbaMem[BBA_NCRB] & NCRB_PM)
489 {
490 // Accept all multicast
491 return true;
492 }
493 else
494 {
495 // Lookup the dest eth address in the hashmap
496 u16 index = HashIndex(mRecvBuffer.get());
497 return !!(mBbaMem[BBA_NAFR_MAR0 + index / 8] & (1 << (index % 8)));
498 }
499 }
500
inc_rwp()501 inline void CEXIETHERNET::inc_rwp()
502 {
503 u16* rwp = (u16*)&mBbaMem[BBA_RWP];
504
505 if (*rwp + 1 == page_ptr(BBA_RHBP))
506 *rwp = page_ptr(BBA_BP);
507 else
508 (*rwp)++;
509 }
510
511 // This function is on the critical path for receiving data.
512 // Be very careful about calling into the logger and other slow things
RecvHandlePacket()513 bool CEXIETHERNET::RecvHandlePacket()
514 {
515 u8* write_ptr;
516 u8* end_ptr;
517 u8* read_ptr;
518 Descriptor* descriptor;
519 u32 status = 0;
520 u16 rwp_initial = page_ptr(BBA_RWP);
521
522 if (!RecvMACFilter())
523 goto wait_for_next;
524
525 #ifdef BBA_TRACK_PAGE_PTRS
526 INFO_LOG(SP1, "RecvHandlePacket %x\n%s", mRecvBufferLength,
527 ArrayToString(mRecvBuffer, mRecvBufferLength, 0x100).c_str());
528
529 INFO_LOG(SP1, "%x %x %x %x", page_ptr(BBA_BP), page_ptr(BBA_RRP), page_ptr(BBA_RWP),
530 page_ptr(BBA_RHBP));
531 #endif
532
533 write_ptr = ptr_from_page_ptr(BBA_RWP);
534 end_ptr = ptr_from_page_ptr(BBA_RHBP);
535 read_ptr = ptr_from_page_ptr(BBA_RRP);
536
537 descriptor = (Descriptor*)write_ptr;
538 write_ptr += 4;
539
540 for (u32 i = 0, off = 4; i < mRecvBufferLength; ++i, ++off)
541 {
542 *write_ptr++ = mRecvBuffer[i];
543
544 if (off == 0xff)
545 {
546 off = 0;
547 inc_rwp();
548 }
549
550 if (write_ptr == end_ptr)
551 write_ptr = ptr_from_page_ptr(BBA_BP);
552
553 if (write_ptr == read_ptr)
554 {
555 /*
556 halt copy
557 if (cur_packet_size >= PAGE_SIZE)
558 desc.status |= FO | BF
559 if (RBFIM)
560 raise RBFI
561 if (AUTORCVR)
562 discard bad packet
563 else
564 inc MPC instead of receiving packets
565 */
566 status |= DESC_FO | DESC_BF;
567 mBbaMem[BBA_IR] |= mBbaMem[BBA_IMR] & INT_RBF;
568 break;
569 }
570 }
571
572 // Align up to next page
573 if ((mRecvBufferLength + 4) % 256)
574 inc_rwp();
575
576 #ifdef BBA_TRACK_PAGE_PTRS
577 INFO_LOG(SP1, "%x %x %x %x", page_ptr(BBA_BP), page_ptr(BBA_RRP), page_ptr(BBA_RWP),
578 page_ptr(BBA_RHBP));
579 #endif
580
581 // Is the current frame multicast?
582 if (mRecvBuffer[0] & 0x01)
583 status |= DESC_MF;
584
585 if (status & DESC_BF)
586 {
587 if (mBbaMem[BBA_MISC2] & MISC2_AUTORCVR)
588 {
589 *(u16*)&mBbaMem[BBA_RWP] = rwp_initial;
590 }
591 else
592 {
593 ERROR_LOG(SP1, "RBF while AUTORCVR == 0!");
594 }
595 }
596
597 descriptor->set(*(u16*)&mBbaMem[BBA_RWP], 4 + mRecvBufferLength, status);
598
599 mBbaMem[BBA_LRPS] = status;
600
601 // Raise interrupt
602 if (mBbaMem[BBA_IMR] & INT_R)
603 {
604 mBbaMem[BBA_IR] |= INT_R;
605
606 exi_status.interrupt |= exi_status.TRANSFER;
607 ExpansionInterface::ScheduleUpdateInterrupts(CoreTiming::FromThread::NON_CPU, 0);
608 }
609 else
610 {
611 // This occurs if software is still processing the last raised recv interrupt
612 WARN_LOG(SP1, "NOT raising recv interrupt");
613 }
614
615 wait_for_next:
616 if (mBbaMem[BBA_NCRA] & NCRA_SR)
617 m_network_interface->RecvStart();
618
619 return true;
620 }
621 } // namespace ExpansionInterface
622