Reversed Little Endian

From WiiBrew
Jump to navigation Jump to search

Device registers are usually specified as having a certain byte order. For example, the Wii's native byte order is Big Endian, and all of its custom registers use that format. However, a problem arises when connecting standard devices specified as being in one endianness to a device whose CPU uses the opposite byte order. This is the case of the Wii's SD, USB, and WiFi controllers, which are nominally little-endian.

There are two solutions: connect the peripheral as is (requires byte swapping every time a register access is made), or swap the byte lanes. The Wii opts to swap the byte lanes. Unfortunately, register sizes vary, so swapping the byte lanes gives interesting results when 8-bit or 16-bit registers are used. Essentially, every 32-bit unit is swapped with respect to the little-endian version of the peripheral. This means that a sequence of three registers {u8 a; u8 b; u16 c;} packed into a 32-bit word will look like {u16 c; u8 b; u8 a;} on the Wii, with c in the correct big-endian byte order.

To make things even more confusing, there are implementation bugs and the byte enable lines don't operate as they should. Therefore, you should always read and write in 32-bit units, and use read-modify-write to access 8-bit or 16-bit subregisters.

Typically, in order to have the rest of the device code be compatible with the standard implementation, specific read and write wrappers are used which perform this work for you:

u8 __sd_read8(u32 addr)
{
	u32 mask;
	u8 shift;

	shift = (addr & 3) * 8;
	mask = (0xFF << shift);

	return (read32(addr & ~3) & mask) >> shift;
}

u16 __sd_read16(u32 addr)
{
	if(addr & 3)
		return (read32(addr & ~3) & 0xffff0000) >> 16;
	else
		return (read32(addr) & 0xffff);
}

inline u32 __sd_read32(u32 addr)
{
	return read32(addr);
}

void __sd_write8(u32 addr, u8 data)
{
	u32 mask;
	u8 shift;

	shift = (addr & 3) * 8;
	mask = (0xFF << shift);

	mask32(addr & ~3, mask, data << shift);
}

void __sd_write16(u32 addr, u16 data)
{
	if(addr & 3)
		mask32(addr & ~3, 0xffff0000, data << 16);
	else
		mask32(addr, 0xffff, ((u32)data));
}

inline void __sd_write32(u32 addr, u32 data)
{
	write32(addr, data);
}

Reversed Little Endian byte order is equivalent to Big Endian byte order for 32-bit registers. This happens with all but the very first of the EHCI controller's registers, for example.