/* Copyright 2016-2024 melonDS team This file is part of melonDS. melonDS is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. melonDS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with melonDS. If not, see http://www.gnu.org/licenses/. */ #include #include #include "NDS.h" #include "SPI.h" #include "Wifi.h" #include "WifiAP.h" #include "Platform.h" namespace melonDS { using Platform::Log; using Platform::LogLevel; //#define WIFI_LOG printf #define WIFI_LOG(...) {} #define PRINT_MAC(pf, mac) Log(LogLevel::Debug, "%s: %02X:%02X:%02X:%02X:%02X:%02X\n", pf, (mac)[0], (mac)[1], (mac)[2], (mac)[3], (mac)[4], (mac)[5]); #define IOPORT(x) IO[(x)>>1] #define IOPORT8(x) ((u8*)IO)[x] // destination MACs for MP frames const u8 Wifi::MPCmdMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x00}; const u8 Wifi::MPReplyMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x10}; const u8 Wifi::MPAckMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x03}; // multiplayer host TX sequence: // 1. preamble // 2. IRQ7 // 3. send data // 4. optional IRQ1 // 5. wait for client replies (duration: 16 + ((10 * CMD_REPLYTIME) * numclients) + ack preamble (96)) // 6. IRQ7 // 7. send ack (32 bytes) // 8. optional IRQ1, along with IRQ12 if the transfer was successful or if // there's no time left for a retry // // if the transfer has to be retried (for example, didn't get replies from all clients) // and there is time, it repeats the sequence // // if there isn't enough time left on CMD_COUNT, IRQ12 is triggered alone when // CMD_COUNT is 10, and the packet txheader[0] is set to 5 // // RFSTATUS values: // 0 = initial // 1 = waiting for incoming packets // 2 = switching from RX to TX // 3 = TX // 4 = switching from TX to RX // 5 = MP host data sent, waiting for replies (RFPINS=0x0084) // 6 = RX // 7 = switching from RX reply to TX ack // 8 = MP client sending reply, MP host sending ack (RFPINS=0x0046) // 9 = idle // wifi TODO: // * RXSTAT // * TX errors (if applicable) bool MACEqual(const u8* a, const u8* b) { return (*(u32*)&a[0] == *(u32*)&b[0]) && (*(u16*)&a[4] == *(u16*)&b[4]); } bool MACIsBroadcast(const u8* a) { return (*(u32*)&a[0] == 0xFFFFFFFF) && (*(u16*)&a[4] == 0xFFFF); } Wifi::Wifi(melonDS::NDS& nds) : NDS(nds) { NDS.RegisterEventFunc(Event_Wifi, 0, MemberEventFunc(Wifi, USTimer)); WifiAP = new class WifiAP(this, NDS.UserData); } Wifi::~Wifi() { delete WifiAP; WifiAP = nullptr; NDS.UnregisterEventFunc(Event_Wifi, 0); } void Wifi::Reset() { memset(RAM, 0, 0x2000); memset(IO, 0, 0x1000); Enabled = false; PowerOn = false; Random = 1; memset(BBRegs, 0, 0x100); memset(BBRegsRO, 0, 0x100); #define BBREG_FIXED(id, val) BBRegs[id] = val; BBRegsRO[id] = 1; BBREG_FIXED(0x00, 0x6D); BBREG_FIXED(0x0D, 0x00); BBREG_FIXED(0x0E, 0x00); BBREG_FIXED(0x0F, 0x00); BBREG_FIXED(0x10, 0x00); BBREG_FIXED(0x11, 0x00); BBREG_FIXED(0x12, 0x00); BBREG_FIXED(0x16, 0x00); BBREG_FIXED(0x17, 0x00); BBREG_FIXED(0x18, 0x00); BBREG_FIXED(0x19, 0x00); BBREG_FIXED(0x1A, 0x00); BBREG_FIXED(0x27, 0x00); BBREG_FIXED(0x4D, 0x00); // 00 or BF BBREG_FIXED(0x5D, 0x01); BBREG_FIXED(0x5E, 0x00); BBREG_FIXED(0x5F, 0x00); BBREG_FIXED(0x60, 0x00); BBREG_FIXED(0x61, 0x00); BBREG_FIXED(0x64, 0xFF); // FF or 3F BBREG_FIXED(0x66, 0x00); for (int i = 0x69; i < 0x100; i++) { BBREG_FIXED(i, 0x00); } #undef BBREG_FIXED const Firmware& fw = NDS.SPI.GetFirmware(); const auto& fwheader = fw.GetHeader(); RFVersion = fwheader.RFChipType; memset(RFRegs, 0, 4*0x40); // load channel index/data from the firmware // the current channel will be determined by RF settings // so we compare the two 'most important' RF registers to these values to figure out which channel is selected if (RFVersion == 3) { RFChannelIndex[0] = fwheader.Type3Config.RFIndex1; RFChannelIndex[1] = fwheader.Type3Config.RFIndex2; for (int i = 0; i < 14; i++) { RFChannelData[i][0] = fwheader.Type3Config.RFData1[i]; RFChannelData[i][1] = fwheader.Type3Config.RFData2[i]; } } else { RFChannelIndex[0] = fwheader.Type2Config.InitialRF56Values[2] >> 2; RFChannelIndex[1] = fwheader.Type2Config.InitialRF56Values[5] >> 2; for (int i = 0; i < 14; i++) { RFChannelData[i][0] = fwheader.Type2Config.InitialRF56Values[i*6 + 0] | (fwheader.Type2Config.InitialRF56Values[i*6 + 1] << 8) | ((fwheader.Type2Config.InitialRF56Values[i*6 + 2] & 0x03) << 16); RFChannelData[i][1] = fwheader.Type2Config.InitialRF56Values[i*6 + 3] | (fwheader.Type2Config.InitialRF56Values[i*6 + 4] << 8) | ((fwheader.Type2Config.InitialRF56Values[i*6 + 5] & 0x03) << 16); } } CurChannel = 0; Firmware::FirmwareConsoleType console = fwheader.ConsoleType; if (console == Firmware::FirmwareConsoleType::DS) IOPORT(0x000) = 0x1440; else if (console == Firmware::FirmwareConsoleType::DSLite) IOPORT(0x000) = 0xC340; else if (NDS.ConsoleType == 1 && console == Firmware::FirmwareConsoleType::DSi) IOPORT(0x000) = 0xC340; // DSi has the modern DS-wifi variant else { Log(LogLevel::Warn, "wifi: unknown console type %02X\n", console); IOPORT(0x000) = 0x1440; } memset(&IOPORT(0x018), 0xFF, 6); memset(&IOPORT(0x020), 0xFF, 6); // TODO: find out what the initial values are IOPORT(W_PowerUS) = 0x0001; //IOPORT(W_BeaconInterval) = 100; USTimestamp = 0; USCounter = 0; USCompare = 0; BlockBeaconIRQ14 = false; memset(TXSlots, 0, sizeof(TXSlots)); memset(TXBuffer, 0, sizeof(TXBuffer)); ComStatus = 0; TXCurSlot = -1; RXCounter = 0; memset(RXBuffer, 0, sizeof(RXBuffer)); RXBufferPtr = 0; RXTime = 0; RXHalfwordTimeMask = 0xFFFFFFFF; MPReplyTimer = 0; MPClientMask = 0; MPClientFail = 0; memset(MPClientReplies, 0, sizeof(MPClientReplies)); MPLastSeqno = 0xFFFF; CmdCounter = 0; USUntilPowerOn = 0; IsMP = false; IsMPClient = false; NextSync = 0; RXTimestamp = 0; WifiAP->Reset(); } void Wifi::DoSavestate(Savestate* file) { file->Section("WIFI"); // berp. // not sure we're saving enough shit at all there. // also: savestate and wifi can't fucking work together!! // or it can but you would be disconnected file->VarArray(RAM, 0x2000); file->VarArray(IO, 0x1000); file->Bool32(&Enabled); file->Bool32(&PowerOn); file->Var16(&Random); file->Var32((u32*)&TimerError); file->VarArray(BBRegs, 0x100); file->VarArray(BBRegsRO, 0x100); file->Var8(&RFVersion); file->VarArray(RFRegs, 4*0x40); file->Var32((u32*)&CurChannel); file->Var64(&USCounter); file->Var64(&USCompare); file->Bool32(&BlockBeaconIRQ14); file->Var32(&CmdCounter); file->Var64(&USTimestamp); for (int i = 0; i < 6; i++) { TXSlot* slot = &TXSlots[i]; file->Bool32(&slot->Valid); file->Var16(&slot->Addr); file->Var16(&slot->Length); file->Var8(&slot->Rate); file->Var8(&slot->CurPhase); file->Var32((u32*)&slot->CurPhaseTime); file->Var32(&slot->HalfwordTimeMask); } file->VarArray(TXBuffer, sizeof(TXBuffer)); file->VarArray(RXBuffer, sizeof(RXBuffer)); file->Var32(&RXBufferPtr); file->Var32((u32*)&RXTime); file->Var32(&RXHalfwordTimeMask); file->Var32(&ComStatus); file->Var32(&TXCurSlot); file->Var32(&RXCounter); file->Var32((u32*)&MPReplyTimer); file->Var16(&MPClientMask); file->Var16(&MPClientFail); file->VarArray(MPClientReplies, sizeof(MPClientReplies)); file->Var16(&MPLastSeqno); file->Var32((u32*)&USUntilPowerOn); file->Bool32(&IsMP); file->Bool32(&IsMPClient); file->Var64(&NextSync); file->Var64(&RXTimestamp); } void Wifi::ScheduleTimer(bool first) { if (first) TimerError = 0; s32 cycles = 33513982 * kTimerInterval; cycles -= TimerError; s32 delay = (cycles + 999999) / 1000000; TimerError = (delay * 1000000) - cycles; NDS.ScheduleEvent(Event_Wifi, !first, delay, 0, 0); } void Wifi::UpdatePowerOn() { bool on = Enabled; if (NDS.ConsoleType == 1) { // TODO for DSi: // * W_POWER_US doesn't work (atleast on DWM-W024) // * other registers like GPIO_WIFI may also control wifi power/clock // * turning wifi off via POWCNT2 while sending breaks further attempts at sending frames } else { on = on && ((IOPORT(W_PowerUS) & 0x1) == 0); } if (on == PowerOn) return; PowerOn = on; if (on) { Log(LogLevel::Debug, "WIFI: ON\n"); ScheduleTimer(true); Platform::MP_Begin(NDS.UserData); } else { Log(LogLevel::Debug, "WIFI: OFF\n"); NDS.CancelEvent(Event_Wifi); Platform::MP_End(NDS.UserData); } } void Wifi::SetPowerCnt(u32 val) { Enabled = val & (1<<1); UpdatePowerOn(); } void Wifi::CheckIRQ(u16 oldflags) { u16 newflags = IOPORT(W_IF) & IOPORT(W_IE); if ((oldflags == 0) && (newflags != 0)) NDS.SetIRQ(1, IRQ_Wifi); } void Wifi::SetIRQ(u32 irq) { u16 oldflags = IOPORT(W_IF) & IOPORT(W_IE); IOPORT(W_IF) |= (1<Addr + 0x4]; if (cnt < 0xFF) cnt++; *(u16*)&RAM[slot->Addr + 0x4] = cnt; } void Wifi::ReportMPReplyErrors(u16 clientfail) { // TODO: do these trigger any IRQ? for (int i = 1; i < 16; i++) { if (!(clientfail & (1<Addr + 0x4]) { noseqno = 2; } else { if (num == 1) noseqno = (IOPORT(W_TXSlotCmd) & 0x4000) ? 1:0; } if (!noseqno) { if (!(IOPORT(W_TXHeaderCnt) & (1<<2))) *(u16*)&RAM[slot->Addr + 0xC + 22] = IOPORT(W_TXSeqNo) << 4; IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; } u16 framectl = *(u16*)&RAM[slot->Addr + 0xC]; if (framectl & (1<<14)) { // WEP frame // TODO: what happens when sending a WEP frame while WEP processing is off? // TODO: some form of actual WEP processing? // for now we just set the WEP FCS to a nonzero value, because some games require it if (IOPORT(W_WEPCnt) & (1<<15)) { u32 wep_fcs = (slot->Addr + 0xC + slot->Length - 7) & ~0x1; *(u32*)&RAM[wep_fcs] = 0x22334466; } } int len = slot->Length; if ((slot->Addr + len) > 0x1FF4) len = 0x1FF4 - slot->Addr; memcpy(TXBuffer, &RAM[slot->Addr], 12+len); if (noseqno == 2) *(u16*)&TXBuffer[0xC] |= (1<<11); if (CurChannel == 0) return; TXBuffer[9] = CurChannel; switch (num) { case 0: case 2: case 3: Platform::MP_SendPacket(TXBuffer, 12+len, USTimestamp, NDS.UserData); if (!IsMP) WifiAP->SendPacket(TXBuffer, 12+len); break; case 1: *(u16*)&TXBuffer[12 + 24+2] = MPClientMask; Platform::MP_SendCmd(TXBuffer, 12+len, USTimestamp, NDS.UserData); break; case 5: IncrementTXCount(slot); Platform::MP_SendReply(TXBuffer, 12+len, USTimestamp, IOPORT(W_AIDLow), NDS.UserData); break; case 4: *(u64*)&TXBuffer[0xC + 24] = USCounter; Platform::MP_SendPacket(TXBuffer, 12+len, USTimestamp, NDS.UserData); break; } } void Wifi::StartTX_LocN(int nslot, int loc) { TXSlot* slot = &TXSlots[nslot]; if (IOPORT(W_TXSlotLoc1 + (loc*4)) & 0x7000) Log(LogLevel::Warn, "wifi: unusual loc%d bits set %04X\n", loc, IOPORT(W_TXSlotLoc1 + (loc*4))); slot->Valid = true; slot->Addr = (IOPORT(W_TXSlotLoc1 + (loc*4)) & 0x0FFF) << 1; slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; u8 rate = RAM[slot->Addr + 0x8]; if (rate == 0x14) slot->Rate = 2; else slot->Rate = 1; slot->CurPhase = 0; slot->CurPhaseTime = PreambleLen(slot->Rate); } void Wifi::StartTX_Cmd() { TXSlot* slot = &TXSlots[1]; if (IOPORT(W_TXSlotCmd) & 0x3000) Log(LogLevel::Warn,"wifi: !! unusual TXSLOT_CMD bits set %04X\n", IOPORT(W_TXSlotCmd)); slot->Valid = true; slot->Addr = (IOPORT(W_TXSlotCmd) & 0x0FFF) << 1; slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; u8 rate = RAM[slot->Addr + 0x8]; if (rate == 0x14) slot->Rate = 2; else slot->Rate = 1; MPClientMask = *(u16*)&RAM[slot->Addr + 12 + 24 + 2] & MPClientFail; MPClientFail &= MPClientMask; u32 duration = PreambleLen(slot->Rate) + (slot->Length * (slot->Rate==2 ? 4:8)); duration += 112 + ((10 + IOPORT(W_CmdReplyTime)) * NumClients(MPClientMask)); duration += (32 * (slot->Rate==2 ? 4:8)); if (CmdCounter > (duration + 100)) { slot->CurPhase = 0; slot->CurPhaseTime = PreambleLen(slot->Rate); } else { slot->CurPhase = 13; slot->CurPhaseTime = CmdCounter - 100; } // starting a CMD transfer wakes up the transceiver automatically UpdatePowerStatus(1); } void Wifi::StartTX_Beacon() { TXSlot* slot = &TXSlots[4]; slot->Valid = true; slot->Addr = (IOPORT(W_TXSlotBeacon) & 0x0FFF) << 1; slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; u8 rate = RAM[slot->Addr + 0x8]; if (rate == 0x14) slot->Rate = 2; else slot->Rate = 1; slot->CurPhase = 0; slot->CurPhaseTime = PreambleLen(slot->Rate); IOPORT(W_TXBusy) |= 0x0010; } void Wifi::FireTX() { if (!(IOPORT(W_RXCnt) & 0x8000)) return; u16 txbusy = IOPORT(W_TXBusy); u16 txreq = IOPORT(W_TXReqRead); u16 txstart = 0; if (IOPORT(W_TXSlotLoc1) & 0x8000) txstart |= 0x0001; if (IOPORT(W_TXSlotCmd ) & 0x8000) txstart |= 0x0002; if (IOPORT(W_TXSlotLoc2) & 0x8000) txstart |= 0x0004; if (IOPORT(W_TXSlotLoc3) & 0x8000) txstart |= 0x0008; txstart &= txreq; txstart &= ~txbusy; IOPORT(W_TXBusy) = txbusy | txstart; if (txstart & 0x0008) { StartTX_LocN(3, 2); return; } if (txstart & 0x0004) { StartTX_LocN(2, 1); return; } if (txstart & 0x0002) { MPClientFail = 0xFFFE; StartTX_Cmd(); return; } if (txstart & 0x0001) { StartTX_LocN(0, 0); return; } } void Wifi::SendMPDefaultReply() { u8 reply[12 + 28]; *(u16*)&reply[0xA] = 28; // length // rate //if (TXSlots[1].Rate == 2) reply[0x8] = 0x14; //else reply[0x8] = 0xA; // TODO reply[0x8] = 0x14; if (CurChannel == 0) return; reply[0x9] = CurChannel; *(u16*)&reply[0xC + 0x00] = 0x0158; *(u16*)&reply[0xC + 0x02] = 0x00F0;//0; // TODO?? *(u16*)&reply[0xC + 0x04] = IOPORT(W_BSSID0); *(u16*)&reply[0xC + 0x06] = IOPORT(W_BSSID1); *(u16*)&reply[0xC + 0x08] = IOPORT(W_BSSID2); *(u16*)&reply[0xC + 0x0A] = IOPORT(W_MACAddr0); *(u16*)&reply[0xC + 0x0C] = IOPORT(W_MACAddr1); *(u16*)&reply[0xC + 0x0E] = IOPORT(W_MACAddr2); *(u16*)&reply[0xC + 0x10] = 0x0903; *(u16*)&reply[0xC + 0x12] = 0x00BF; *(u16*)&reply[0xC + 0x14] = 0x1000; *(u16*)&reply[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; *(u32*)&reply[0xC + 0x18] = 0; int txlen = Platform::MP_SendReply(reply, 12+28, USTimestamp, IOPORT(W_AIDLow), NDS.UserData); WIFI_LOG("wifi: sent %d/40 bytes of MP default reply\n", txlen); } void Wifi::SendMPReply(u16 clienttime, u16 clientmask) { TXSlot* slot = &TXSlots[5]; // mark the last packet as success. dunno what the MSB is, it changes. //if (slot->Valid) if (IOPORT(W_TXSlotReply2) & 0x8000) *(u16*)&RAM[slot->Addr] = 0x0001; // CHECKME!! // can the transfer rate for MP replies be set, or is it determined from the CMD transfer rate? // how does it work for default empty replies? slot->Rate = 2; IOPORT(W_TXSlotReply2) = IOPORT(W_TXSlotReply1); IOPORT(W_TXSlotReply1) = 0; if (!(IOPORT(W_TXSlotReply2) & 0x8000)) { slot->Valid = false; } else { slot->Valid = true; slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; // the packet is entirely ignored if it lasts longer than the maximum reply time u32 duration = PreambleLen(slot->Rate) + (slot->Length * (slot->Rate==2 ? 4:8)); if (duration > clienttime) slot->Valid = false; } // this seems to be set upon IRQ0 // TODO: how does it behave if the packet addr is changed before it gets sent? (maybe just not possible) if (slot->Valid) { slot->CurPhase = 0; TXSendFrame(slot, 5); } else { slot->CurPhase = 10; SendMPDefaultReply(); } u16 clientnum = 0; for (int i = 1; i < IOPORT(W_AIDLow); i++) { if (clientmask & (1<CurPhaseTime = 16 + ((clienttime + 10) * clientnum) + PreambleLen(slot->Rate); IOPORT(W_TXBusy) |= 0x0080; } void Wifi::SendMPAck(u16 cmdcount, u16 clientfail) { u8 ack[12 + 32]; *(u16*)&ack[0xA] = 32; // length // rate if (TXSlots[1].Rate == 2) ack[0x8] = 0x14; else ack[0x8] = 0xA; if (CurChannel == 0) return; ack[0x9] = CurChannel; *(u16*)&ack[0xC + 0x00] = 0x0218; *(u16*)&ack[0xC + 0x02] = 0; *(u16*)&ack[0xC + 0x04] = 0x0903; *(u16*)&ack[0xC + 0x06] = 0x00BF; *(u16*)&ack[0xC + 0x08] = 0x0300; *(u16*)&ack[0xC + 0x0A] = IOPORT(W_BSSID0); *(u16*)&ack[0xC + 0x0C] = IOPORT(W_BSSID1); *(u16*)&ack[0xC + 0x0E] = IOPORT(W_BSSID2); *(u16*)&ack[0xC + 0x10] = IOPORT(W_MACAddr0); *(u16*)&ack[0xC + 0x12] = IOPORT(W_MACAddr1); *(u16*)&ack[0xC + 0x14] = IOPORT(W_MACAddr2); *(u16*)&ack[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; *(u16*)&ack[0xC + 0x18] = cmdcount; *(u16*)&ack[0xC + 0x1A] = clientfail; *(u32*)&ack[0xC + 0x1C] = 0; if (!clientfail) { u32 nextbeacon; if (IOPORT(W_TXBusy) & 0x0010) nextbeacon = 0; else nextbeacon = ((IOPORT(W_BeaconCount1) - 1) << 10) + (0x400 - (USCounter & 0x3FF)); int runahead = std::min(CmdCounter, nextbeacon); if (CmdCounter < 1000) runahead -= 210; *(u32*)&ack[0] = std::max(runahead - (32*(TXSlots[1].Rate==2?4:8)), 0); } else { *(u32*)&ack[0] = PreambleLen(TXSlots[1].Rate); } int txlen = Platform::MP_SendAck(ack, 12+32, USTimestamp, NDS.UserData); WIFI_LOG("wifi: sent %d/44 bytes of MP ack, %d %d\n", txlen, ComStatus, RXTime); } bool Wifi::ProcessTX(TXSlot* slot, int num) { slot->CurPhaseTime -= kTimerInterval; if (slot->CurPhaseTime > 0) { if (slot->CurPhase == 1) { if (!(slot->CurPhaseTime & slot->HalfwordTimeMask)) IOPORT(W_RXTXAddr)++; } else if (slot->CurPhase == 2) { MPReplyTimer -= kTimerInterval; if (MPReplyTimer <= 0 && MPClientMask != 0) { int nclient = 1; while (!(MPClientMask & (1 << nclient))) nclient++; u32 curclient = 1 << nclient; if (!(MPClientFail & curclient)) MPClientReplyRX(nclient); MPReplyTimer += 10 + IOPORT(W_CmdReplyTime); MPClientMask &= ~curclient; } } return false; } switch (slot->CurPhase) { case 0: // preamble done { SetIRQ(7); if (num == 5) SetStatus(8); else SetStatus(3); u32 len = slot->Length; if (slot->Rate == 2) { len *= 4; slot->HalfwordTimeMask = 0x7 & kTimeCheckMask; } else { len *= 8; slot->HalfwordTimeMask = 0xF & kTimeCheckMask; } slot->CurPhase = 1; slot->CurPhaseTime = len; // set TX addr IOPORT(W_RXTXAddr) = slot->Addr >> 1; if (num != 5) { TXSendFrame(slot, num); } // if the packet is being sent via LOC1..3, send it to the AP // any packet sent via CMD/REPLY/BEACON isn't going to have much use outside of local MP if (num == 0 || num == 2 || num == 3) { u16 framectl = *(u16*)&RAM[slot->Addr + 0xC]; if ((framectl & 0x00FF) == 0x0010) { u16 aid = *(u16*)&RAM[slot->Addr + 0xC + 24 + 4]; if (aid) Log(LogLevel::Debug, "[HOST] syncing client %04X, sync=%016llX\n", aid, USTimestamp); } else if ((framectl & 0x00FF) == 0x00C0) { if (IsMPClient) { Log(LogLevel::Info, "[CLIENT] deauth\n"); IsMP = false; IsMPClient = false; } } } } break; case 10: // preamble done (default empty MP reply) { SetIRQ(7); SetStatus(8); slot->CurPhase = 11; slot->CurPhaseTime = 28*4; slot->HalfwordTimeMask = 0xFFFFFFFF; } break; case 1: // transmit done { // for the MP CMD and reply slots, this is set later if (num != 1 && num != 5) *(u16*)&RAM[slot->Addr] = 0x0001; RAM[slot->Addr + 5] = 0; if (num == 1) { if (IOPORT(W_TXStatCnt) & 0x4000) { IOPORT(W_TXStat) = 0x0800; SetIRQ(1); } SetStatus(5); MPReplyTimer = 16 + PreambleLen(slot->Rate); u16 res = 0; if (MPClientMask) res = Platform::MP_RecvReplies(MPClientReplies, USTimestamp, MPClientMask, NDS.UserData); MPClientFail &= ~res; // TODO: 112 likely includes the ack preamble, which needs adjusted // for long-preamble settings slot->CurPhase = 2; slot->CurPhaseTime = 112 + ((10 + IOPORT(W_CmdReplyTime)) * NumClients(MPClientMask)); break; } else if (num == 5) { if (IOPORT(W_TXStatCnt) & 0x1000) { IOPORT(W_TXStat) = 0x0401; SetIRQ(1); } SetStatus(1); IOPORT(W_TXBusy) &= ~0x80; FireTX(); return true; } IOPORT(W_TXBusy) &= ~(1<Rate == 2) slot->CurPhaseTime = 32 * 4; else slot->CurPhaseTime = 32 * 8; ReportMPReplyErrors(MPClientFail); // send u16 cmdcount = (CmdCounter + 9) / 10; SendMPAck(cmdcount, MPClientFail); slot->CurPhase = 3; } break; case 3: // MP host ack transfer (reply wait done) { if (!MPClientFail) *(u16*)&RAM[slot->Addr] = 0x0001; else *(u16*)&RAM[slot->Addr] = 0x0005; // this is set to indicate which clients failed to reply *(u16*)&RAM[slot->Addr + 0x2] = MPClientFail; if (!MPClientFail) IncrementTXCount(slot); IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; if (IOPORT(W_TXStatCnt) & 0x2000) { IOPORT(W_TXStat) = 0x0B01; SetIRQ(1); } if (MPClientFail && false) { // if some clients failed to respond: try again // TODO: fix this (causes instability) StartTX_Cmd(); break; } else { IOPORT(W_TXBusy) &= ~(1<<1); IOPORT(W_TXSlotCmd) &= 0x7FFF; SetStatus(1); SetIRQ(12); FireTX(); } } return true; case 13: // MP transfer failed (timeout) { IOPORT(W_TXBusy) &= ~(1<<1); IOPORT(W_TXSlotCmd) &= 0x7FFF; *(u16*)&RAM[slot->Addr] = 0x0005; IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; SetStatus(1); SetIRQ(12); FireTX(); } return true; } return false; } inline void Wifi::IncrementRXAddr(u16& addr, u16 inc) { for (u32 i = 0; i < inc; i += 2) { addr += 2; addr &= 0x1FFE; if (addr == (IOPORT(W_RXBufEnd) & 0x1FFE)) addr = (IOPORT(W_RXBufBegin) & 0x1FFE); } } void Wifi::StartRX() { u16 framelen = *(u16*)&RXBuffer[8]; RXTime = framelen; u16 txrate = *(u16*)&RXBuffer[6]; if (txrate == 0x14) { RXTime *= 4; RXHalfwordTimeMask = 0x7 & kTimeCheckMask; } else { RXTime *= 8; RXHalfwordTimeMask = 0xF & kTimeCheckMask; } u16 addr = IOPORT(W_RXBufWriteCursor) << 1; IncrementRXAddr(addr, 12); IOPORT(W_RXTXAddr) = addr >> 1; RXBufferPtr = 12; SetIRQ(6); SetStatus(6); ComStatus |= 1; } void Wifi::FinishRX() { ComStatus &= ~0x1; RXCounter = 0; if (!ComStatus) { if (IOPORT(W_PowerState) & (1<<9)) { IOPORT(W_TRXPower) = 0; SetStatus(9); } else SetStatus(1); } // TODO: RX stats u16 framectl = *(u16*)&RXBuffer[12]; u16 seqno = *(u16*)&RXBuffer[12 + 22]; // check the frame's destination address // note: the hardware always checks the first address field, regardless of the frame type/etc // similarly, the second address field is used to send acks to non-broadcast frames u8* dstmac = &RXBuffer[12 + 4]; if (!(dstmac[0] & 0x01)) { if (!MACEqual(dstmac, (u8*)&IOPORT(W_MACAddr0))) return; } // reject the frame if it's a WEP frame and WEP is off // TODO: check if sending WEP frames with WEP off works at all? if (framectl & (1<<14)) { if (!(IOPORT(W_WEPCnt) & (1<<15))) return; } // apply RX filtering // TODO: // * RXFILTER bits 0, 9, 10, 12 not fully understood // * port 0D8 also affects reception of frames // * MP CMD frames with a duplicate sequence number are ignored u16 rxflags = 0x0010; bool cmd_dupe = false; switch ((framectl >> 2) & 0x3) { case 0: // management { u8* bssid = &RXBuffer[12 + 16]; if (MACEqual(bssid, (u8*)&IOPORT(W_BSSID0))) rxflags |= 0x8000; u16 subtype = (framectl >> 4) & 0xF; if (subtype == 0x8) // beacon { if (!(rxflags & 0x8000)) { if (!(IOPORT(W_RXFilter) & (1<<0))) return; } rxflags |= 0x0001; } else if ((subtype <= 0x5) || (subtype >= 0xA && subtype <= 0xC)) { if (!(rxflags & 0x8000)) { // CHECKME! if (!(IOPORT(W_RXFilter) & (3<<9))) return; } } } break; case 1: // control { if ((framectl & 0xF0) == 0xA0) // PS-poll { u8* bssid = &RXBuffer[12 + 4]; if (MACEqual(bssid, (u8*)&IOPORT(W_BSSID0))) rxflags |= 0x8000; if (!(rxflags & 0x8000)) { if (!(IOPORT(W_RXFilter) & (1<<11))) return; } rxflags |= 0x0005; } else return; } break; case 2: // data { u16 fromto = (framectl >> 8) & 0x3; if (IOPORT(W_RXFilter2) & (1<> 4) & 0xF) { case 0x0: break; case 0x1: if ((rxflags & 0xF) == 0xD) { if (!(rxfilter & (1<<7))) return; } else if ((rxflags & 0xF) != 0xE) { if (!(rxfilter & (1<<1))) return; } break; case 0x2: if ((rxflags & 0xF) != 0xC) { if (!(rxfilter & (1<<2))) return; } break; case 0x3: if (!(rxfilter & (1<<3))) return; break; case 0x4: break; case 0x5: if ((rxflags & 0xF) == 0xF) { if (!(rxfilter & (1<<8))) return; } else { if (!(rxfilter & (1<<4))) return; } break; case 0x6: if (!(rxfilter & (1<<5))) return; break; case 0x7: if (!(rxfilter & (1<<6))) return; break; default: return; } } break; } if (!cmd_dupe) { // build the RX header u16 headeraddr = IOPORT(W_RXBufWriteCursor) << 1; *(u16*)&RAM[headeraddr] = rxflags; IncrementRXAddr(headeraddr); *(u16*)&RAM[headeraddr] = 0x0040; // ??? IncrementRXAddr(headeraddr, 4); *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[6]; // TX rate IncrementRXAddr(headeraddr); *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[8]; // frame length IncrementRXAddr(headeraddr); *(u16*)&RAM[headeraddr] = 0x4080; // RSSI // signal successful reception u16 addr = IOPORT(W_RXTXAddr) << 1; if (addr & 0x2) IncrementRXAddr(addr); IOPORT(W_RXBufWriteCursor) = (addr & ~0x3) >> 1; SetIRQ(0); } if ((rxflags & 0x800F) == 0x800C) { // reply to CMD frames u16 clientmask = *(u16*)&RXBuffer[0xC + 26]; if (IOPORT(W_AIDLow) && (clientmask & (1 << IOPORT(W_AIDLow)))) { SendMPReply(*(u16*)&RXBuffer[0xC + 24], clientmask); } else { // send a blank // this is just so the host can have something to receive, instead of hitting a timeout // in the case this client wasn't ready to send a reply // TODO: also send this if we have RX disabled Platform::MP_SendReply(nullptr, 0, USTimestamp, 0, NDS.UserData); } } else if ((rxflags & 0x800F) == 0x8001) { // when receiving a beacon with the right BSSID, the beacon's timestamp // is copied to USCOUNTER u32 len = *(u16*)&RXBuffer[8]; u16 txrate = *(u16*)&RXBuffer[6]; len *= ((txrate==0x14) ? 4 : 8); len -= 76; // CHECKME: is this offset fixed? u64 timestamp = *(u64*)&RXBuffer[12 + 24]; timestamp += (u64)len; USCounter = timestamp; } } void Wifi::MPClientReplyRX(int client) { if (IOPORT(W_PowerState) & (1<<9)) return; if (!(IOPORT(W_RXCnt) & 0x8000)) return; if (IOPORT(W_RXBufBegin) == IOPORT(W_RXBufEnd)) return; int framelen; u8 txrate; u8* reply = &MPClientReplies[(client-1)*1024]; framelen = *(u16*)&reply[10]; txrate = reply[8]; // TODO: what are the maximum crop values? u16 framectl = *(u16*)&reply[12]; if (framectl & (1<<14)) { framelen -= (IOPORT(W_RXLenCrop) >> 7) & 0x1FE; if (framelen > 24) memmove(&RXBuffer[12+24], &RXBuffer[12+28], framelen); } else framelen -= (IOPORT(W_RXLenCrop) << 1) & 0x1FE; if (framelen < 0) framelen = 0; // TODO rework RX system so we don't need this (by reading directly into MPClientReplies) memcpy(RXBuffer, reply, 12+framelen); *(u16*)&RXBuffer[6] = txrate; *(u16*)&RXBuffer[8] = framelen; RXTimestamp = 0; StartRX(); } bool Wifi::CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames { if (IOPORT(W_PowerState) & (1<<9)) return false; if (!(IOPORT(W_RXCnt) & 0x8000)) return false; if (IOPORT(W_RXBufBegin) == IOPORT(W_RXBufEnd)) return false; int rxlen; int framelen; u16 framectl; u8 txrate, chan; u64 timestamp; for (;;) { timestamp = 0; if (type == 0) { rxlen = Platform::MP_RecvPacket(RXBuffer, ×tamp, NDS.UserData); if ((rxlen <= 0) && (!IsMP)) rxlen = WifiAP->RecvPacket(RXBuffer); } else { rxlen = Platform::MP_RecvHostPacket(RXBuffer, ×tamp, NDS.UserData); if (rxlen < 0) { // host is gone // TODO: make this more resilient IsMP = false; IsMPClient = false; } } if (rxlen <= 0) return false; if (rxlen < 12+24) continue; framelen = *(u16*)&RXBuffer[10]; if (framelen != rxlen-12) { Log(LogLevel::Error, "bad frame length %d/%d\n", framelen, rxlen-12); continue; } chan = RXBuffer[9]; if (chan != CurChannel || CurChannel == 0) { Log(LogLevel::Debug, "received frame but bad channel %d (expected %d)\n", chan, CurChannel); continue; } // hack: ignore MP frames if not engaged in a MP comm if (type == 0 && (!IsMP)) { if (MACEqual(&RXBuffer[12 + 16], MPReplyMAC) || MACEqual(&RXBuffer[12 + 4], MPCmdMAC) || MACEqual(&RXBuffer[12 + 4], MPReplyMAC)) { continue; } } framectl = *(u16*)&RXBuffer[12+0]; txrate = RXBuffer[8]; // TODO: what are the maximum crop values? if (framectl & (1<<14)) { framelen -= (IOPORT(W_RXLenCrop) >> 7) & 0x1FE; if (framelen > 24) memmove(&RXBuffer[12+24], &RXBuffer[12+28], framelen); } else framelen -= (IOPORT(W_RXLenCrop) << 1) & 0x1FE; if (framelen < 0) framelen = 0; break; } WIFI_LOG("wifi: received packet FC:%04X SN:%04X CL:%04X RXT:%d CMT:%d\n", framectl, *(u16*)&RXBuffer[12+4+6+6+6], *(u16*)&RXBuffer[12+4+6+6+6+2+2], framelen*4, IOPORT(W_CmdReplyTime)); *(u16*)&RXBuffer[6] = txrate; *(u16*)&RXBuffer[8] = framelen; u16 frametype = (framectl & 0x00FF); bool macgood = (RXBuffer[12 + 4] & 0x01) || MACEqual(&RXBuffer[12 + 4], (u8*)&IOPORT(W_MACAddr0)); // HACK: when receiving auth/assoc frames, extend the post-beacon interval // during MP comm, the host will periodically wake up to send a beacon, and stay awake during the // post-beacon interval to see if any clients are trying to connect // the auth/assoc procedure would normally fit during that window, but when we are emulating wifi // and not yet synced, these frames may lag behind, preventing a successful connection if ((frametype == 0x00B0 || frametype == 0x0010 || frametype == 0x0000) && timestamp && macgood) { if (IOPORT(W_BeaconCount2)) IOPORT(W_BeaconCount2) += 10; } if ((frametype == 0x0010) && timestamp && macgood) { // if receiving an association response: get the sync value from the host u16 aid = *(u16*)&RXBuffer[12+24+4]; if (aid) { Log(LogLevel::Debug, "[CLIENT %01X] host sync=%016llX\n", aid&0xF, timestamp); IsMP = true; IsMPClient = true; USTimestamp = timestamp; NextSync = RXTimestamp + (framelen * (txrate==0x14 ? 4:8)); } RXTimestamp = 0; StartRX(); } else if ((frametype == 0x00C0) && timestamp && macgood && IsMPClient) { IsMP = false; IsMPClient = false; NextSync = 0; RXTimestamp = 0; StartRX(); } else if (macgood && IsMPClient) { // if we are being a MP client, we need to delay this frame until we reach the // timestamp it came with // we also need to determine how far we can run after having received this frame RXTimestamp = timestamp; if (RXTimestamp < USTimestamp) RXTimestamp = USTimestamp; NextSync = RXTimestamp + (framelen * (txrate==0x14 ? 4:8)); if (MACEqual(&RXBuffer[12 + 4], MPCmdMAC)) { u16 clienttime = *(u16*)&RXBuffer[12+24]; u16 clientmask = *(u16*)&RXBuffer[12+26]; // include the MP reply time window NextSync += 112 + ((clienttime + 10) * NumClients(clientmask)); } else if (MACEqual(&RXBuffer[12 + 4], MPAckMAC)) { u32 runahead = *(u32*)&RXBuffer[0]; NextSync += runahead; } } else { // otherwise, just start receiving this frame now RXTimestamp = 0; StartRX(); } return true; } void Wifi::MSTimer() { if (IOPORT(W_USCompareCnt)) { if ((USCounter & ~0x3FF) == USCompare) { BlockBeaconIRQ14 = false; SetIRQ14(0); } } if (IOPORT(W_BeaconCount1) != 0) { IOPORT(W_BeaconCount1)--; if (IOPORT(W_BeaconCount1) == 0) SetIRQ14(1); } if (IOPORT(W_BeaconCount1) == 0) IOPORT(W_BeaconCount1) = IOPORT(W_BeaconInterval); if (IOPORT(W_BeaconCount2) != 0) { IOPORT(W_BeaconCount2)--; if (IOPORT(W_BeaconCount2) == 0) SetIRQ13(); } } void Wifi::USTimer(u32 param) { USTimestamp += kTimerInterval; if (IsMPClient && (!ComStatus)) { if (RXTimestamp && (USTimestamp >= RXTimestamp)) { RXTimestamp = 0; StartRX(); } if (USTimestamp >= NextSync) { // TODO: not do this every tick if it fails to receive a frame! CheckRX(2); } } if (!(USTimestamp & 0x3FF & kTimeCheckMask)) WifiAP->MSTimer(); if (USUntilPowerOn < 0) { USUntilPowerOn += kTimerInterval; if (USUntilPowerOn >= 0) { USUntilPowerOn = 0; IOPORT(W_PowerState) = 0; SetStatus(1); UpdatePowerStatus(0); } } if (IOPORT(W_USCountCnt)) { USCounter += kTimerInterval; u32 uspart = (USCounter & 0x3FF); if (IOPORT(W_USCompareCnt)) { u32 beaconus = (IOPORT(W_BeaconCount1) << 10) | (0x3FF - uspart); if ((beaconus & kTimeCheckMask) == (IOPORT(W_PreBeacon) & kTimeCheckMask)) SetIRQ15(); } if (!(uspart & kTimeCheckMask)) MSTimer(); } if (IOPORT(W_CmdCountCnt) & 0x0001) { if (CmdCounter > 0) { if (CmdCounter < kTimerInterval) CmdCounter = 0; else CmdCounter -= kTimerInterval; } } if (IOPORT(W_ContentFree) != 0) { if (IOPORT(W_ContentFree) < kTimerInterval) IOPORT(W_ContentFree) = 0; else IOPORT(W_ContentFree) -= kTimerInterval; } if (ComStatus == 0) { u16 txbusy = IOPORT(W_TXBusy); if (txbusy) { if (IOPORT(W_PowerState) & (1<<9)) { ComStatus = 0; TXCurSlot = -1; } else { ComStatus = 0x2; if (txbusy & 0x0080) TXCurSlot = 5; else if (txbusy & 0x0010) TXCurSlot = 4; else if (txbusy & 0x0008) TXCurSlot = 3; else if (txbusy & 0x0004) TXCurSlot = 2; else if (txbusy & 0x0002) TXCurSlot = 1; else if (txbusy & 0x0001) TXCurSlot = 0; } } else { if ((!IsMPClient) || (USTimestamp > NextSync)) { if ((!(RXCounter & 0x1FF & kTimeCheckMask)) && (!ComStatus)) { CheckRX(0); } } RXCounter += kTimerInterval; } } if (ComStatus & 0x2) { bool finished = ProcessTX(&TXSlots[TXCurSlot], TXCurSlot); if (finished) { if (IOPORT(W_PowerState) & (1<<9)) { IOPORT(W_TXBusy) = 0; IOPORT(W_TRXPower) = 0; SetStatus(9); } // transfer finished, see if there's another slot to do // checkme: priority order of beacon/reply // TODO: for CMD, check CMDCOUNT u16 txbusy = IOPORT(W_TXBusy); if (txbusy & 0x0080) TXCurSlot = 5; else if (txbusy & 0x0010) TXCurSlot = 4; else if (txbusy & 0x0008) TXCurSlot = 3; else if (txbusy & 0x0004) TXCurSlot = 2; else if (txbusy & 0x0002) TXCurSlot = 1; else if (txbusy & 0x0001) TXCurSlot = 0; else { TXCurSlot = -1; ComStatus = 0; RXCounter = 0; } } } if (ComStatus & 0x1) { RXTime -= kTimerInterval; if (!(RXTime & RXHalfwordTimeMask)) { u16 addr = IOPORT(W_RXTXAddr) << 1; if (addr < 0x1FFF) *(u16*)&RAM[addr] = *(u16*)&RXBuffer[RXBufferPtr]; IncrementRXAddr(addr); IOPORT(W_RXTXAddr) = addr >> 1; RXBufferPtr += 2; if (RXTime <= 0) // finished receiving { FinishRX(); } else if (addr == (IOPORT(W_RXBufReadCursor) << 1)) { // TODO: properly check the crossing of the read cursor // (for example, if it is outside of the RX buffer) Log(LogLevel::Debug, "wifi: RX buffer full (buf=%04X/%04X rd=%04X wr=%04X rxtx=%04X power=%04X com=%d rxcnt=%04X filter=%04X/%04X frame=%04X/%04X len=%d)\n", (IOPORT(W_RXBufBegin)>>1)&0xFFF, (IOPORT(W_RXBufEnd)>>1)&0xFFF, IOPORT(W_RXBufReadCursor), IOPORT(W_RXBufWriteCursor), IOPORT(W_RXTXAddr), IOPORT(W_PowerState), ComStatus, IOPORT(W_RXCnt), IOPORT(W_RXFilter), IOPORT(W_RXFilter2), *(u16*)&RXBuffer[0], *(u16*)&RXBuffer[12], *(u16*)&RXBuffer[8]); RXTime = 0; SetStatus(1); if (TXCurSlot == 0xFFFFFFFF) { ComStatus &= ~0x1; RXCounter = 0; } // TODO: proper error management if ((!ComStatus) && (IOPORT(W_PowerState) & (1<<9))) { IOPORT(W_TRXPower) = 0; SetStatus(9); } } } } ScheduleTimer(false); } void Wifi::ChangeChannel() { u32 val1 = RFRegs[RFChannelIndex[0]]; u32 val2 = RFRegs[RFChannelIndex[1]]; CurChannel = 0; for (int i = 0; i < 14; i++) { if (val1 == RFChannelData[i][0] && val2 == RFChannelData[i][1]) { CurChannel = i+1; break; } } if (CurChannel > 0) Log(LogLevel::Debug, "wifi: switching to channel %d\n", CurChannel); else Log(LogLevel::Debug, "wifi: invalid channel values %05X:%05X\n", val1, val2); } void Wifi::RFTransfer_Type2() { u32 id = (IOPORT(W_RFData2) >> 2) & 0x1F; if (IOPORT(W_RFData2) & 0x0080) { u32 data = RFRegs[id]; IOPORT(W_RFData1) = data & 0xFFFF; IOPORT(W_RFData2) = (IOPORT(W_RFData2) & 0xFFFC) | ((data >> 16) & 0x3); } else { u32 data = IOPORT(W_RFData1) | ((IOPORT(W_RFData2) & 0x0003) << 16); RFRegs[id] = data; if (id == RFChannelIndex[0] || id == RFChannelIndex[1]) ChangeChannel(); } } void Wifi::RFTransfer_Type3() { u32 id = (IOPORT(W_RFData1) >> 8) & 0x3F; u32 cmd = IOPORT(W_RFData2) & 0xF; if (cmd == 6) { IOPORT(W_RFData1) = (IOPORT(W_RFData1) & 0xFF00) | (RFRegs[id] & 0xFF); } else if (cmd == 5) { u32 data = IOPORT(W_RFData1) & 0xFF; RFRegs[id] = data; if (id == RFChannelIndex[0] || id == RFChannelIndex[1]) ChangeChannel(); } } u16 Wifi::Read(u32 addr) { if (addr >= 0x04810000) return 0; addr &= 0x7FFE; if (addr >= 0x4000 && addr < 0x6000) { return *(u16*)&RAM[addr & 0x1FFE]; } if (addr >= 0x2000 && addr < 0x4000) return 0xFFFF; bool activeread = (addr < 0x1000); switch (addr) { case W_Random: // random generator. not accurate // TODO: rotate the sequence based on the ARM7 cycle counter (if this is important) Random = (Random & 0x1) ^ (((Random & 0x3FF) << 1) | (Random >> 10)); return Random; case W_Preamble: return IOPORT(W_Preamble) & 0x0003; case W_USCount0: return (u16)(USCounter & 0xFFFF); case W_USCount1: return (u16)((USCounter >> 16) & 0xFFFF); case W_USCount2: return (u16)((USCounter >> 32) & 0xFFFF); case W_USCount3: return (u16)(USCounter >> 48); case W_USCompare0: return (u16)(USCompare & 0xFFFF); case W_USCompare1: return (u16)((USCompare >> 16) & 0xFFFF); case W_USCompare2: return (u16)((USCompare >> 32) & 0xFFFF); case W_USCompare3: return (u16)(USCompare >> 48); case W_CmdCount: return (CmdCounter + 9) / 10; case W_BBRead: if ((IOPORT(W_BBCnt) & 0xF000) != 0x6000) { Log(LogLevel::Error, "WIFI: bad BB read, CNT=%04X\n", IOPORT(W_BBCnt)); return 0; } return BBRegs[IOPORT(W_BBCnt) & 0xFF]; case W_BBBusy: return 0; // TODO eventually (BB busy flag) case W_RFBusy: return 0; // TODO eventually (RF busy flag) case W_RXBufDataRead: if (activeread) { u32 rdaddr = IOPORT(W_RXBufReadAddr); u16 ret = *(u16*)&RAM[rdaddr]; rdaddr += 2; if (rdaddr == (IOPORT(W_RXBufEnd) & 0x1FFE)) rdaddr = (IOPORT(W_RXBufBegin) & 0x1FFE); if (rdaddr == IOPORT(W_RXBufGapAddr)) { rdaddr += (IOPORT(W_RXBufGapSize) << 1); if (rdaddr >= (IOPORT(W_RXBufEnd) & 0x1FFE)) rdaddr = rdaddr + (IOPORT(W_RXBufBegin) & 0x1FFE) - (IOPORT(W_RXBufEnd) & 0x1FFE); if (IOPORT(0x000) == 0xC340) IOPORT(W_RXBufGapSize) = 0; } IOPORT(W_RXBufReadAddr) = rdaddr & 0x1FFE; IOPORT(W_RXBufDataRead) = ret; if (IOPORT(W_RXBufCount) > 0) { IOPORT(W_RXBufCount)--; if (IOPORT(W_RXBufCount) == 0) SetIRQ(9); } } break; case W_TXBusy: return IOPORT(W_TXBusy) & 0x001F; // no bit for MP replies. odd case W_CMDStat0: case W_CMDStat1: case W_CMDStat2: case W_CMDStat3: case W_CMDStat4: case W_CMDStat5: case W_CMDStat6: case W_CMDStat7: { u16 ret = IOPORT(addr&0xFFF); IOPORT(addr&0xFFF) = 0; return ret; } } return IOPORT(addr&0xFFF); } void Wifi::Write(u32 addr, u16 val) { if (addr >= 0x04810000) return; addr &= 0x7FFE; if (addr >= 0x4000 && addr < 0x6000) { *(u16*)&RAM[addr & 0x1FFE] = val; return; } if (addr >= 0x2000 && addr < 0x4000) return; switch (addr) { case W_ModeReset: { u16 oldval = IOPORT(W_ModeReset); IOPORT(W_ModeReset) = val & 0x0001; if (!(oldval & 0x0001) && (val & 0x0001)) { IOPORT(0x27C) = 0x0005; // TODO: 02A2?? UpdatePowerStatus(0); } else if ((oldval & 0x0001) && !(val & 0x0001)) { IOPORT(0x27C) = 0x000A; UpdatePowerStatus(0); } if (val & 0x2000) { IOPORT(W_RXBufWriteAddr) = 0; IOPORT(W_CmdTotalTime) = 0; IOPORT(W_CmdReplyTime) = 0; IOPORT(0x1A4) = 0; IOPORT(0x278) = 0x000F; // TODO: other ports?? } if (val & 0x4000) { IOPORT(W_ModeWEP) = 0; IOPORT(W_TXStatCnt) = 0; IOPORT(0x00A) = 0; IOPORT(W_MACAddr0) = 0; IOPORT(W_MACAddr1) = 0; IOPORT(W_MACAddr2) = 0; IOPORT(W_BSSID0) = 0; IOPORT(W_BSSID1) = 0; IOPORT(W_BSSID2) = 0; IOPORT(W_AIDLow) = 0; IOPORT(W_AIDFull) = 0; IOPORT(W_TXRetryLimit) = 0x0707; IOPORT(0x02E) = 0; IOPORT(W_RXBufBegin) = 0x4000; IOPORT(W_RXBufEnd) = 0x4800; IOPORT(W_TXBeaconTIM) = 0; IOPORT(W_Preamble) = 0x0001; IOPORT(W_RXFilter) = 0x0401; IOPORT(0x0D4) = 0x0001; IOPORT(W_RXFilter2) = 0x0008; IOPORT(0x0EC) = 0x3F03; IOPORT(W_TXHeaderCnt) = 0; IOPORT(0x198) = 0; IOPORT(0x1A2) = 0x0001; IOPORT(0x224) = 0x0003; IOPORT(0x230) = 0x0047; } } return; case W_ModeWEP: val &= 0x007F; IOPORT(W_ModeWEP) = val; if (IOPORT(W_PowerTX) & (1<<1)) { if ((val & 0x7) == 1) IOPORT(W_PowerDownCtrl) |= (1<<1); else if ((val & 0x7) == 2) IOPORT(W_PowerDownCtrl) = 3; if ((val & 0x7) != 3) IOPORT(W_PowerState) &= 0x0300; UpdatePowerStatus(0); } return; case W_IE: { u16 oldflags = IOPORT(W_IF) & IOPORT(W_IE); IOPORT(W_IE) = val; CheckIRQ(oldflags); } return; case W_IF: IOPORT(W_IF) &= ~val; return; case W_IFSet: { u16 oldflags = IOPORT(W_IF) & IOPORT(W_IE); IOPORT(W_IF) |= (val & 0xFBFF); CheckIRQ(oldflags); Log(LogLevel::Debug, "wifi: force-setting IF %04X\n", val); } return; case W_AIDLow: IOPORT(W_AIDLow) = val & 0x000F; return; case W_AIDFull: IOPORT(W_AIDFull) = val & 0x07FF; return; case W_PowerUS: IOPORT(W_PowerUS) = val & 0x0003; UpdatePowerOn(); return; case W_PowerTX: IOPORT(W_PowerTX) = val & 0x0003; if (val & (1<<1)) { if ((IOPORT(W_ModeWEP) & 0x7) == 1) IOPORT(W_PowerDownCtrl) |= (1<<1); else if ((IOPORT(W_ModeWEP) & 0x7) == 2) IOPORT(W_PowerDownCtrl) = 3; UpdatePowerStatus(0); } return; case W_PowerState: if ((IOPORT(W_ModeWEP) & 0x7) != 3) return; val = (IOPORT(W_PowerState) & 0x0300) | (val & 0x0003); if ((val & 0x0300) == 0x0200) val &= ~(1<<0); else val &= ~(1<<1); if (!(val & (1<<9))) val &= ~(1<<8); IOPORT(W_PowerState) = val; UpdatePowerStatus(0); return; case W_PowerForce: val &= 0x8001; IOPORT(W_PowerForce) = val; UpdatePowerStatus(0); return; case W_PowerDownCtrl: IOPORT(W_PowerDownCtrl) = val & 0x0003; if (IOPORT(W_PowerTX) & (1<<1)) { if ((IOPORT(W_ModeWEP) & 0x7) == 1) IOPORT(W_PowerDownCtrl) |= (1<<1); else if ((IOPORT(W_ModeWEP) & 0x7) == 2) IOPORT(W_PowerDownCtrl) = 3; } if (val != 0 && val != 3) Log(LogLevel::Warn, "wifi: unusual W_PowerDownCtrl value %04X\n", val); UpdatePowerStatus(0); return; case W_USCountCnt: val &= 0x0001; break; case W_USCompareCnt: if (val & 0x0002) SetIRQ14(2); val &= 0x0001; break; case W_USCount0: USCounter = (USCounter & 0xFFFFFFFFFFFF0000) | (u64)val; return; case W_USCount1: USCounter = (USCounter & 0xFFFFFFFF0000FFFF) | ((u64)val << 16); return; case W_USCount2: USCounter = (USCounter & 0xFFFF0000FFFFFFFF) | ((u64)val << 32); return; case W_USCount3: USCounter = (USCounter & 0x0000FFFFFFFFFFFF) | ((u64)val << 48); return; case W_USCompare0: USCompare = (USCompare & 0xFFFFFFFFFFFF0000) | (u64)(val & 0xFC00); if (val & 0x0001) BlockBeaconIRQ14 = true; return; case W_USCompare1: USCompare = (USCompare & 0xFFFFFFFF0000FFFF) | ((u64)val << 16); return; case W_USCompare2: USCompare = (USCompare & 0xFFFF0000FFFFFFFF) | ((u64)val << 32); return; case W_USCompare3: USCompare = (USCompare & 0x0000FFFFFFFFFFFF) | ((u64)val << 48); return; case W_CmdCount: CmdCounter = val * 10; return; case W_BBCnt: IOPORT(W_BBCnt) = val; if ((IOPORT(W_BBCnt) & 0xF000) == 0x5000) { u32 regid = IOPORT(W_BBCnt) & 0xFF; if (!BBRegsRO[regid]) BBRegs[regid] = IOPORT(W_BBWrite) & 0xFF; } return; case W_RFData2: IOPORT(W_RFData2) = val; if (RFVersion == 3) RFTransfer_Type3(); else RFTransfer_Type2(); return; case W_RFCnt: val &= 0x413F; break; case W_RXCnt: if (val & 0x0001) { IOPORT(W_RXBufWriteCursor) = IOPORT(W_RXBufWriteAddr); } if (val & 0x0080) { IOPORT(W_TXSlotReply2) = IOPORT(W_TXSlotReply1); IOPORT(W_TXSlotReply1) = 0; } if (val & 0x8000) { FireTX(); } val &= 0xFF0E; if (val & 0x7FFF) Log(LogLevel::Warn, "wifi: unknown RXCNT bits set %04X\n", val); break; case W_RXBufDataRead: Log(LogLevel::Warn, "wifi: writing to RXBUF_DATA_READ. wat\n"); if (IOPORT(W_RXBufCount) > 0) { IOPORT(W_RXBufCount)--; if (IOPORT(W_RXBufCount) == 0) SetIRQ(9); } return; case W_RXBufReadAddr: case W_RXBufGapAddr: val &= 0x1FFE; break; case W_RXBufGapSize: case W_RXBufCount: case W_RXBufWriteAddr: case W_RXBufReadCursor: val &= 0x0FFF; break; case W_TXReqReset: IOPORT(W_TXReqRead) &= ~val; return; case W_TXReqSet: IOPORT(W_TXReqRead) |= val; FireTX(); return; case W_TXSlotReset: if (val & 0x0001) IOPORT(W_TXSlotLoc1) &= 0x7FFF; if (val & 0x0002) IOPORT(W_TXSlotCmd) &= 0x7FFF; if (val & 0x0004) IOPORT(W_TXSlotLoc2) &= 0x7FFF; if (val & 0x0008) IOPORT(W_TXSlotLoc3) &= 0x7FFF; // checkme: any bits affecting the beacon slot? if (val & 0x0040) IOPORT(W_TXSlotReply2) &= 0x7FFF; if (val & 0x0080) IOPORT(W_TXSlotReply1) &= 0x7FFF; if ((val & 0xFF30) && (val != 0xFFFF)) Log(LogLevel::Warn, "unusual TXSLOTRESET %04X\n", val); val = 0; // checkme (write-only port) break; case W_TXBufDataWrite: { u32 wraddr = IOPORT(W_TXBufWriteAddr); *(u16*)&RAM[wraddr] = val; wraddr += 2; if (wraddr == IOPORT(W_TXBufGapAddr)) wraddr += (IOPORT(W_TXBufGapSize) << 1); //if (IOPORT(0x000) == 0xC340) // IOPORT(W_TXBufGapSize) = 0; IOPORT(W_TXBufWriteAddr) = wraddr & 0x1FFE; if (IOPORT(W_TXBufCount) > 0) { IOPORT(W_TXBufCount)--; if (IOPORT(W_TXBufCount) == 0) SetIRQ(8); } } return; case W_TXBufWriteAddr: case W_TXBufGapAddr: val &= 0x1FFE; break; case W_TXBufGapSize: case W_TXBufCount: val &= 0x0FFF; break; case W_TXSlotBeacon: IsMP = (val & 0x8000) != 0; break; case W_TXSlotCmd: if (CmdCounter == 0) val = (val & 0x7FFF) | (IOPORT(W_TXSlotCmd) & 0x8000); // fall-through case W_TXSlotLoc1: case W_TXSlotLoc2: case W_TXSlotLoc3: // checkme: is it possible to cancel a queued transfer that hasn't started yet // by clearing bit15 here? IOPORT(addr&0xFFF) = val; FireTX(); return; case 0x228: case 0x244: //printf("wifi: write port%03X %04X\n", addr, val); break; // read-only ports case 0x000: case 0x034: case 0x044: case 0x054: case 0x098: case 0x0B0: case 0x0B6: case 0x0B8: case 0x15C: case 0x15E: case 0x180: case 0x19C: case 0x1A8: case 0x1AC: case 0x1C4: case 0x210: case 0x214: case 0x268: return; default: //printf("WIFI unk: write %08X %04X\n", addr, val); break; } IOPORT(addr&0xFFF) = val; } const u8* Wifi::GetMAC() const { return (u8*)&IOPORT(W_MACAddr0); } const u8* Wifi::GetBSSID() const { return (u8*)&IOPORT(W_BSSID0); } }