-->
我目前使用的是MPC8309,我不确定大小端内核是什么时候转换的。
今天看了一篇关于readl和writel具体实现的文章
今天我们主要分析readl/writel如何实现高效的数据交换和寄存器读写。让我们以 readl 为例来了解如何处理大端处理器的寄存器数据。
内核下的
readl定义如下,在include/asm-generic/io.h
#define readw(addr) __le32_to_cpu(__raw_readw(addr))
__raw_readl是最底层的寄存器读写函数。很简单,直接获取寄存器数据即可。我们看一下__le32_to_cpu的实现。该函数对于字节顺序有不同的实现。对于little-endian处理器,它位于./include/linux/byteorder/little_endian.h中,如下:
#define __le32_to_cpu(x) ((__force __u32)(__le32)(x))
相当于什么都不做。对于big-endian处理器,在./include/linux/byteorder/big_endian.h中,如下:
#define __le32_to_cpu(x) __swab32((__force __u32)(__le32)(x))
从字面意思也可以看出,__swab32实现了数据翻转。后面我们会分析__swab32的实现。本质就在于这个函数。
但在此之前,我们先考虑一个问题。对于不同的CPU,比如arm mips ppc,如何选择使用little_endian.h或者big_endian.h。
答案是,针对不同的处理器平台,都有 arch/xxx/include/asm/byteorder.h 头文件。我们看一下arm mips ppc的byteorder.h。
arch/arm/include/asm/byteorder.h
- * arch/arm/include/asm/byteorder.h
- *
- * ARM 字节序。在小端模式下,数据总线的连接方式如下
- * 字节访问显示为:
- * 0 = d0...d7、1 = d8...d15、2 = d16...d23、3 = d24...d31
- * 和字访问(数据或指令)显示为:
- * d0...d31
- *
- * 在大端模式下,字节访问显示为:
- * 0 = d24...d31, 1 = d16...d23, 2 = d8...d15, 3 = d0...d7
- * 和字访问(数据或指令)显示为:
- * d0...d31
- */
- #ifndef __ASM_ARM_BYTEORDER_H
- #define __ASM_ARM_BYTEORDER_H
- #ifdef __ARMEB__
- #包括
- #else
- #包括
- #endif
- #endif
arch/mips/include/asm/byteorder.h
- /*
- * 此文件受 GNU 通用公共
的条款和条件约束
- * 许可证。查看此存档主目录中的文件“COPYING”
- * 了解更多详情。
- *
- * 版权所有 (C) 1996、99、2003,作者:Ralf Baechle
- */
- #ifndef _ASM_BYTEORDER_H
- #define _ASM_BYTEORDER_H
- #if 已定义(__MIPSEB__)
- #包括
- #elif 定义(__MIPSEL__)
- #包括
- #else
- # 错误“MIPS,但既不是 __MIPSEB__,也不是 __MIPSEL__???”
- #endif
- #endif /*_ASM_BYTEORDER_H*/
arch/powerpc/include/asm/byteorder.h
- #ifndef _ASM_POWERPC_BYTEORDER_H
- #define _ASM_POWERPC_BYTEORDER_H
- /*
- * 该程序是免费软件;您可以重新分发它和/或
- * 根据 GNU 通用公共许可证
的条款对其进行修改
- * 由自由软件基金会发布;任一版本
- * 2 个许可证,或(由您选择)任何更高版本。
- */
- #包括
- #endif /* _ASM_POWERPC_BYTEORDER_H */
可以看出arm mips在内核下支持big endian和small endian,并且arm mips确实可以选择处理器字节序。 ppc 仅支持 big-endian。 (其实ppc也支持选择字节顺序)
每个处理器平台的byteorder.h将littlie_endian.h/big_endian.h包装在另一层中。我们在编写驱动程序时不需要关心处理器的字节顺序,我们只需要包含byteorder.h。
我们来看看最关键的__swab32函数,如下:
在 include/linux/swab.h 中
- /**
- * __swab32 - 返回字节交换的 32 位值
- * @x:转换为字节交换的值
- */
- #define __swab32(x) \
- (__builtin_constant_p((__u32)(x)) ? \
- ___constant_swab32(x):\
- __fswab32(x))
宏定义扩展是一个条件运算符。
__builtin_constant_p是gcc的内置函数,用于在编译时判断一个值是否为常量。如果参数是常量,函数返回1,否则返回0。
如果数据是常量,则__constant_swab32实现如下:
- #define ___constant_swab32(x) ((__u32)(
- (((__u32)(x) & (__u32)0x000000ffUL) << 24) |
- (((__u32)(x) & (__u32)0x0000ff00UL) << 8) |
- (((__u32)(x) & (__u32)0x00ff0000UL) >> 8) |
- (((__u32)(x) & (__u32)0xff000000UL) >> 24)))
对于常数数据,采用普通位移然后拼接的方法。对于常量来说,这样的消耗是必要的(这是内核的解释,不是很好理解)
如果数据是运行时计算数据,则使用__fswab32,实现如下:
- 静态内联 __attribute_const__ __u32 __fswab32(__u32 val)
- {
- #ifdef __arch_swab32
- 返回__arch_swab32(val);
- #else
- 返回___constant_swab32(val);
- #endif
- }
如果没有定义__arch_swab32,则仍然使用__constant_swab32方法来翻转数据。然而arm mips ppc都为各自的平台定义了__arch_swab32,以实现针对自己平台的高效交换。它们的定义如下:
arch/arm/include/asm/swab.h
- 静态内联 __attribute_const__ __u32 __arch_swab32(__u32 x)
- {
- __asm__ ("rev %0, %1" : "=r" (x) : "r" (x));
- 返回x;
- }
arch/mips/include/asm/swab.h
- 静态内联 __attribute_const__ __u32 __arch_swab32(__u32 x)
- {
- __asm__(
- " wsbh %0, %1 \n"
- " 旋转 %0, %0, 16 \n"
- :“=r”(x)
- : "r" (x));
- 返回x;
- }
arch/powerpc/include/asm/swab.h
- 静态内联 __attribute_const__ __u32 __arch_swab32(__u32 值)
- {
- __u32 结果;
- __asm__("rlwimi %0,%1,24,16,23\n\t"
- "rlwimi %0,%1,8,8,15\n\t"
- "rlwimi %0,%1,24,0,7"
- :“=r”(结果)
- :“r”(值),“0”(值 >> 24));
- 返回结果;
- }
可以看到arm使用1条指令(rev数据翻转指令),mips使用2条指令(wsbh rotr数据交换指令),ppc使用3条指令(rlwimi数据移位指令)完成32位数据的翻转。这比普通的位移拼接方式效率要高很多!
其实从函数名__fswab就可以看出,是要实现快速交换的。
-->