Only one session can be open per service at a time. If a session is already open for a service, MCU module will wait for the thread handling the session to terminate(triggered by the session being closed by the user process), then it accepts the new session. The commands for each service are handled by separate threads.

MCU camera service "mcu::CAM"

Command Header Description
0x00010040 WriteCameraLedState (writes i2c register 0x2B)
0x00020080 ReadCameraLedState (reads i2c register 0x2B)

MCU GPU service "mcu::GPU"

Command Header Description
0x00010000 GetLcdPowerState. This writes the value of I2C-MCU register 0xf bit6 to u8 cmdreply[2], and the value of bit5 from that register to u8 cmdreply[3].
0x00020080 SetLcdPowerState. This writes the upper LCD bits of MCU register 0x22.
0x00030000 GetGpuLcdInterfaceState. This writes the value of I2C-MCU register 0xf bit7 to u8 cmdreply[2].
0x00040040 SetGpuLcdInterfaceState. This writes the lower two bits of MCU register 0x22.
0x00050040 SetTopScreenFlicker
0x00060080 GetTopScreenFlicker
0x00070040 SetBottomScreenFlicker
0x00080080 GetBottomScreenFlicker
0x00090000 GetMcuFwVerHigh. Called by GSP module
0x000A0000 GetMcuFwVerLow. Called by GSP module
0x000B0040 Set3dLedState
0x000C0000 Get3dLedState
0x000D0000 GetMcuGpuEventHandle. Event handle written to TLS+0x8c. MCU notifications 24 to 29 signal this.
0x000E0000 GetMcuGpuEventReason. Writes some value to TLS+0x88. Called by GSP module

MCU HID service "mcu::HID"

Command Header Description
0x00010040 ?
0x00020000 ??? test register 0x40 bit0, and writes result to IPC+8
0x00030040 ??? writes IPC+4 to register 0x41
0x00040000 ??? reads register 0x44 to IPC+8
0x00050080 ??? writes IPC+4 to register 0x43 and IPC+8 to register 0x44
0x00060000 ReadGyroscopeValues (reads gyroscopy with corrected values)
0x00070000 GetRaw3DSliderPosition
0x00080040 ?
0x00090000 ?
0x000A0040 ?
0x000B0000 ?
0x000C0000 GetMcuHidEventHandle. MCU notifications 11 and 12 signal this. Handle is written to IPC+12
0x000D0000 GetMcuHidEventReason. This reads an internal flield into IPC+8 and clears it.
0x000E0000 GetSoundVolume
0x000F0040 EnableAccelerometerInterrupt(int enable). 1 = enable, 0 = disable accelerometer

While before these functions are handled, the MCU interrupt with bitmask 0x800 is enabled, then after the commands were handled the MCU interrupt bits 0x1800 get cleared.

MCU service "mcu::RTC"

Command Header Description
0x0001.... SetSystemClock (RTC)
0x0002.... GetSystemClock (RTC)
0x0003.... ?
0x0004.... GetSystemClockSeconds
0x0005.... ?
0x0006.... GetSystemClockMinutes ?
0x0007.... ?
0x0008....
0x0009.... ?
0x000A.... ?
0x000B.... ?
0x000C.... ?
0x000D.... ?
0x000E.... ?
0x000F.... ?
0x0010.... ?
0x0011.... ?
0x0012.... ?
0x0013.... ?
0x0014.... ?
0x0015.... ?
0x0016.... ?
0x0017.... ?
0x0018.... ?
0x0019.... ?
0x001A.... ?
0x001B.... ?
0x001C.... ?
0x001D.... ?
0x001E.... ?
0x001F0040 SetPedometerRecordingMode
0x00200000 GetPedometerState
0x00210080 GetStepCount(for the current day)
0x0022.... ?
0x0023.... ?
0x0024.... GetMcuRtcEventHandle. MCU notifications 1, 8, 9, 10, 13, 14 and 15 signal this.
0x0025.... GetMcuRtcEventReason
0x0026.... ?
0x0027.... Disables i2c register 2
0x0028.... ?
0x0029.... Same as 0x0027....
0x002A0000 GetShellState. This writes the value of I2C-MCU register 0xf bit1 to u8 cmdreply[2].
0x002B0000 GetAdapterState. This writes the value of I2C-MCU register 0xf bit3 to u8 cmdreply[2].
0x002C0000 GetBatteryChargeState. This writes the value of I2C-MCU register 0xf bit4 to u8 cmdreply[2].
0x002D0000 GetBatteryLevel
0x002E.... ?
0x002F.... ?
0x0030.... ?
0x0031.... ?
0x0032.... PowerOff (writes 0x1 to i2c MCU device, reg 0x20)
0x0033.... HardwareReboot (writes 0x4 to i2c MCU device, reg 0x20)
0x0034.... ?
0x0035.... Writes 0x10 to i2c MCU device, reg 0x20
0x0036.... SetWatchdogTimer
0x0037.... GetWatchdogTimer
0x0038.... ?
0x0039.... ?
0x003A.... ?
0x003B0640 SetInfoLEDPattern
0x003C0040 SetInfoLEDPatternHeader
0x003D0000 GetInfoLEDStatus
0x003E.... ?
0x003F.... ?
0x0040.... ?
0x0041.... ?
0x00420040 SetBatteryEmptyLEDPattern
0x0043.... ?
0x0044.... ?
0x0045.... ?
0x0046.... ?
0x0047.... ?
0x0048.... ?
0x0049.... ?
0x004A.... ?
0x004B.... ?
0x004C.... ?
0x004D.... ReadHidFlagRegister (reads i2c MCU device, reg 0x10)
0x004E0040 PublishNotifications
0x004F.... Sets some flag (otherwise set when uploading MCU firmware)
0x0050.... Returns the above flag
0x00510040 SetSoftwareClosedFlag
0x00520000 GetSoftwareClosedFlag
0x0053.... ?
0x0054.... ?
0x0055.... ?
0x0056.... ?
0x0057.... ?
0x0058.... ?
0x00590040 SetLegacyJumpProhibitedFlag
0x005A0000 GetLegacyJumpProhibitedFlag

Note that using invalid input with these InfoLED/SetBatteryEmptyLEDPattern commands(especially SetInfoLEDPattern) can cause the system to be bricked(however the boot failure may not begin immediately after using the invalid parameters).

MCU sound service "mcu::SND"

Command Header Description
0x00010080 GetSoundVolume
0x00020040 Set...
0x00030080 Get... cmdbuf[2] is 0 on n3ds

MCU wifi service "mcu::NWM"

Command Header Description
0x0001.... SetWirelessLedState
0x0002.... GetWirelessLedState
0x0003.... Sets GPIO 0x20 high/low?
0x0004.... Gets GPIO 0x20 high/low?
0x0005.... SetEnableWifiGpio
0x0006.... GetEnableWifiGpio
0x0007.... SetWirelessDisabledFlag
0x0008.... GetWirelessDisabledFlag

MCU service "mcu::HWC"

Command Header Description
0x00010082 ReadRegister
0x00020082 WriteRegister
0x00030042 GetInfoRegisters
0x00040000 GetBatteryVoltage
0x00050000 GetBatteryLevel
0x00060040 SetPowerLEDPattern
0x00070040 SetWifiLEDState
0x00080040 SetCameraLEDPattern
0x00090040 Set3DLEDState
0x000A0640 This is the same as MCURTC:SetInfoLEDPattern.
0x000B0000 GetSoundVolume
0x000C0040 SetTopScreenFlicker
0x000D0040 SetBottomScreenFlicker
0x000E0080 ?
0x000F00C0 GetRtcTime
0x00100000 GetMcuFwVerHigh
0x00110000 GetMcuFwVerLow

MCU service "mcu::PLS"

Beg the sysmodule to return the datetime registers in decimal form instead of as a Binary Coded Decimal

Command Header Description
0x00010000 GetDatetime (returns registers 0x30-0x36 in IPC+8)
0x00020000 u8 GetSeconds
0x00030000 u8 GetMinutes
0x00040000 u8 GetHour
0x00050000 u8 GetRegister33h
0x00060000 u8 GetDay
0x00070000 u8 GetMonth
0x00080000 u8 GetYear
0x00090000 u16 GetTickCounter

MCU codec service "mcu::CDC"

Command Header Description
0x00010000 ?

New3DS

The Old3DS/New3DS MCU sysmodules are identical except that the MCU firmware binary written via I2C is different. The size of that binary is the same. The only different words in .text are for the version of that MCU fw binary.

MCU firmware versions

These reside in mcu-module .rodata, are uploaded to MCU register 0x05 and are usually size 0x4003 bytes. (0x4000 bytes with 3 byte magic "jhl"?)

There exists an alternate code path where uploading is done using register 0x3B (decided by making some nonsense conclusions about registers 0x0F and 0x10). This may be a "hack" around early versions of MCU? Register 0x3B is RTC-related on recent versions of MCU, and the "nonsense" condition is not met even on factory MCU firmware.

On dev-units, the user-facing representation of this firmware version is displayed by first subtracting 0x10 from the major field (raw register 0x00). It is these user-facing versions that are displayed in the table below. It is unknown what bit4 (0x10) actually represents, but it is seemingly always set.

Title version Firmware
New3DS v9216 (New2DSXL) 3.65
New3DS v8192/safe v9217 (latest) 3.56
Old3DS v6145 to v8192 (latest) 2.37
Old3DS v5122 2.35
Old3DS v4102 2.30
Old3DS v3072 2.16
Old3DS v2048 1.52
Old3DS v1026 1.51
Old3DS v0/safe v0 1.20
Old3DS factory 1.07