26.8.08

Masalah Endian

(bagian 4 dari 7 artikel - ke artikel utama - ke bagian 5)

Processors store internal data in one of two ways: little-endian or big-endian. Little-endian processors store data with the right-most bytes (those with a higher address value) being the most significant, while big-endian processors store data with the left-most bytes (those with a lower address value) being the most significant.

For example, the table below shows how the decimal value 684686 is stored in a 4-byte integer on the two different processor types (684686 decimal = a728e hex = 00000000 00001010 01110010 10001110 binary)

AddressBig-EndianLittle-Endian
00000000010001110
1 0000101001110010
20111001000001010
31000111000000000

The Intel i386 family are little-endian machines, whereas the SPARC and 68k processors are big-endian. The Alpha, IA-64 and PowerPC processors can be run in either little- or big-endian mode; for Linux the Alpha and IA-64 are typically run in little-endian mode and the PowerPC is run in big-endian mode. The ARM processor can be either, depending on the specific ARM chip being used, the original ARMs were little-endian but for devices designed for network processing they are usually big-endian.

Because of the different endian types of processors, you need to be aware of data you receive from external sources and the order in which it appears. For example, the USB specification dictates that all multibyte data fields are in little-endian form. So if you have a USB driver that reads a multibyte field from the USB connection, you need to convert that data into the processor's native format. Code that assumes the processor is little-endian could ignore the data format coming from the USB connection successfully. But this same code would not work on PowerPC and is the leading cause of drivers that are broken on different platforms. Conversely most networking data is defined in terms of big endian data structures.

Thankfully, there are a number of helpful macros that have been created to make this an easy task. All of the following macros can be found in the asm/byteorder.h header file.

To convert from the processor's native format into little-endian form you can use the following functions:

__le64 cpu_to_le64(u64);
__le32 cpu_to_le32(u32);
__le16 cpu_to_le16(u16);

To convert from little-endian format into the processor's native format you should use these functions:

u64 le64_to_cpu(__le64);
u32 le32_to_cpu(__le32);
u16 le16_to_cpu(__le16);

For big-endian forms, the following functions are available:

__be64 cpu_to_be64(u64);
__be32 cpu_to_be32(u32);
__be16 cpu_to_be16(u16);
u64 be64_to_cpu(__be64);
u32 be32_to_cpu(__be32);
u16 be16_to_cpu(__be16);

If you have a pointer to the value to convert, then you should use the following functions:

__le64 cpu_to_le64p(u64 *);
__le32 cpu_to_le32p(u32 *);
__le16 cpu_to_le16p(u16 *);
u64 le64_to_cpup(__le64 *);
u32 le32_to_cpup(__le32 *);
u16 le16_to_cpup(__le16 *);
__be64 cpu_to_be64p(u64 *);
__be32 cpu_to_be32p(u32 *);
__be16 cpu_to_be16p(u16 *);
u64 be64_to_cpup(__be64 *);
u32 be32_to_cpup(__be32 *);
u16 be16_to_cpup(__be16 *);

If you want to convert the value within a variable and store the modified value in the same variable (in situ), then you should use the following functions:

void cpu_to_le64s(u64 *);
void cpu_to_le32s(u32 *);
void cpu_to_le16s(u16 *);
void le64_to_cpus(u64 *);
void le32_to_cpus(u32 *);
void le16_to_cpus(u16 *);
void cpu_to_be64s(u64 *);
void cpu_to_be32s(u32 *);
void cpu_to_be16s(u16 *);
void be64_to_cpus(u64 *);
void be32_to_cpus(u32 *);
void be16_to_cpus(u16 *);

As stated before, the USB protocol is in little-endian format. The code snippet from drivers/usb/serial/visor.c below shows how a structure is read from the USB connection and then converted into the proper CPU format.

struct visor_connection_info *connection_info;
/* send a get connection info request */
usb_control_msg(serial->dev,
usb_rcvctrlpipe(serial->dev, 0),
VISOR_GET_CONNECTION_INFORMATION,
0xc2, 0x0000, 0x0000,
transfer_buffer, 0x12, 300);
connection_info = (struct visor_connection_info *)transfer_buffer;
num_ports = le16_to_cpu(connection_info->num_ports);

Tidak ada komentar: