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