MIPS N32/N64 ABI 调用约定

MIPS N32 ABI 是 MIPS N64 ABI 的一个变种,在保留 MIPS N64 ABI 的几乎所有特性的情况下,使用了 32-bit 的指针。理由是在一个 32-bit 地址空间足够使用的应用程序上使用 64-bit 的指针不会带来任何好处。虽然它也是 32-bit 指针的,但和 MIPS O32 ABI 的差异还是非常之大的,这也是为什么应该将它归类到 MIPS64 家族中的原因了。

下面我们具体分析一下 MIPS N32 ABI 与 MIPS N64 ABI 的调用约定,会将其中的差异标记出来。

基本数据类型

C类型 汇编名称 MIPS N32 数据宽度(字节) MIPS N64 数据宽度(字节)
char byte 1 1
short half 2 2
int word 4 4
long word 4 8
long long dword 8 8
float word 4 4
double dword 8 8

对齐要求
上述的数据类型,只有当自然对齐的情况下,才可以使用标准的 MIPS 访存指令直接处理。MIPS N32 与 N64 都要求栈对齐到 16 字节。

函数调用约定
常规参数传递
MIPS N32/N64 ABI 约定了多达 8 个通用寄存器(a0-a7)和 8 个双精度浮点寄存器($f12-$f19)用于传递前 8 个参数,其后的所有参数在栈中传递,并且在栈中没有空间保留给前 8 个参数(这一点不同与 MIPS O32 ABI)。所有的常规参数(寄存器、栈中)的都是占用 8 字节空间,寄存器中的参数会符号扩展,就像是加载到寄存器中。通常情况下,前 8 个参数具体是在通用寄存器还是浮点寄存器是由函数原型中的类型决定的,如下例:

void
func (int a, float b, double c, void *d)
{
    // a : GPR a0
    // b : FPR $f13
    // c : FPR $f14
    // d : GPR a3
}
 
func (0, 1.0, 2.1, NULL);

结构体参数传递
C语言允许使用结构体类型作为参数,为了和 MIPS 规则保持一致,被传递的结构体就成为了参数结构中的一部分,其内部结构布局和其通常的存储器映像是完全相同的。在C结构体内,字节或半字紧缩到单个字的存储器单元中,所以当我们通过寄存器来传递概念上属于驻留内存的结构时,我们不得不用数据装满寄存器以模仿存储器的数据排布。如下例:

// Little-Endian
 
struct Arg
{
    char a;
    short b;
    int c;
    double d;
    int e;
};
 
void
test (struct Arg a)
{
    // a, b, c : a0 (value: 0x0000006400018863)
    // d : $f13 (value: 0x4008cccccccccccd)
    // e : a2 (value: 0x000000000000ff00)
}
 
struct Arg a = { 'c', 1, 100, 3.1, 0xff00 };
test (a);

上面的例子中,a0 寄存器的值有些复杂,其中低 8-bit 对应于 a = 0x63,bit8 – bit15 用于对齐,其值无效,bit16 – bit31 对应于 b = 0x0001, bit32 – bit63 对应于 c = 0x00000064。

联合体参数传递
联合体中即有定点又有浮点数据类型时,以此联合体类型作为参数时,通过通用寄存器传递参数。如下例:

union T
{
    uint64_t u;
    double d;
};
 
void
func (int a, union T b)
{
    // a : GPR a0
    // b : GPR a1
}
 
union T ud;
ud.d = 3.1;
func (0xff00, ud);

可变参数传递
目标函数原型是可变参数的情况下,前 8 个参数中确定类型的部分(第一个参数必然是确定的)根据类型传递,其余的参数统一在通用寄存器中传递。如下例:

void
func0 (int a, ...)
{
    // a : GPR a0
    // b : GPR a1
    // c : GPR a2
    // d : GPR a3
}
 
func0 (0, 1, 1.0, NULL);
 
void
func1 (float a, ...)
{
    // a : FPR $f12
    // b : GPR a1
    // c : GPR a2
    // d : GPR a3
}
 
func1 (1.0, 0, 1, 1.0);

返回值传递
在返回基本数据类型的情况下,与常规参数传递一样,有专用的寄存器约定为传递返回值,整型在通用寄存器 v0 中返回,浮点在浮点寄存器 $f0 中返回。比较复杂的情况是返回结构体等导出数据类型,根据返回数据的长度可分为两种情况:
1. 返回数据长度小于等于 16 字节,又按结构体成员的数据类型分为三种情况:
a. 所有成员都是整数型(包含指针)或整数与浮点型混合,返回数据在 v0(低64-bit)和 v1(高64-bit)寄存器传递,寄存器中数据布局与传递结构体参数相同。
b. 所有成员都是浮点型且数量小于等于2,其实就4种组合:float/float, float/double, double/double, double/float,这种情况下使用 f0(第一个成员)和 f2(第二个成员) 寄存器返回。需要格外注意的是 float 类型成员独立占用一个浮点寄存器。
c. 所有成员都是浮点型且数量大于2,返回数据在 v0(低64-bit)和 v1(高64-bit)寄存器传递,寄存器中数据布局与传递结构体参数相同。(其实与情况a相同,独立出来为了引起注意。)
2. 返回数据长度大于 16 字节,则调用者使用 a0 传递一个返回结构体的指针(也就是说调用者分配空间),其原有参数依次后移传递,返回时通过 v0 寄存器返回传入的结构体指针。

// 1.a
struct RetVal
{
    char a;
    int b;
    float c;
};
 
struct RetVal
func (int a)
{
    // a : GPR a0
 
    // return:
    // a : GPR v0 (bit0 - bit7)
    // b : GPR v0 (bit32 - bit63)
    // c : GPR v1 (bit0 - bit31)
}
 
func (0xff);
 
// 1.b
struct RetVal
{
    float a;
    float b;
};
 
struct RetVal
func (int a)
{
    // a : GPR a0
 
    // return:
    // a : FPR f0
    // b : FPR f2
}
 
func (0xff);
 
// 1.c
struct RetVal
{
    float a;
    float b;
    float c;
    float d;
};
 
struct RetVal
func (int a)
{
    // a : GPR a0
 
    // return:
    // a : GPR v0 (bit0 - bit31)
    // b : GPR v0 (bit32 - bit63)
    // c : GPR v1 (bit0 - bit31)
    // d : GPR v1 (bit32 - bit63)
}
 
func (0xff);
 
// 2
struct RetVal
{
    char a;
    int b;
    float c;
    double d;
};
 
struct RetVal
func (int a)
{
    // AddressOf (RetVal) : GPR a0
    // a : GPR a1
 
    // return AddressOf (RetVal) : GPR v0
}
 
func (0xff);

Link: MIPS 寄存器使用约定

Over!

MIPS 使用 synci 指令刷新 Cache

MIPS 是一个缓存不透明的架构设计,通常设计存在两个 Cache,即 指令 Cache 与 数据 Cache。在装载完代码执行前(一般仅应用程序动态生成的代码需要,如 JIT)一定要刷新指令 Cache,确保 Cache 中指令与写入的一致。这个过程分两个步骤:1. 是将数据 Cache 中的数据写回 RAM。2. 将 RAM 中的数据装载到指令 Cache。

较早的实现依赖于 cache 指令,但 cache 指令是个特权指令,事实上并不是需要做这件事的程序都是特权程序,所以普通应用程序在 Linux 平台上可以调用系统调用 cacheflush,进入特权模式完成刷新操作。

上述方法虽然可行,但如果频繁的执行系统调用效率较低,所以在 MIPS r2 的 ISA 中增加了一条常规特权下的 synci 指令,专门用于做这件事。它的用法是:

synci offset(base)

要求对每个 Cache Line 执行一次 synci 操作。

例程

void
cache_flush (void *code, size_t size)
{
       long start, end, line_size, mask;
 
       __asm__ volatile (
               ".set  push                             \t\n"
               ".set  noreorder                        \t\n"
               "rdhwr %[line_size], $1                 \t\n"
               "addu  %[end], %[code], %[size]         \t\n"
               "subu  %[mask], $0, %[line_size]        \t\n"
               "and   %[end], %[end], %[mask]          \t\n"
               "and   %[start], %[code], %[mask]       \t\n"
               "addu  %[end], %[end], %[line_size]     \t\n"
               "1:                                     \t\n"
               "subu  %[end], %[end], %[line_size]     \t\n"
               "bne   %[start], %[end], 1b             \t\n"
               "synci 0(%[end])                        \t\n"
               ".set  pop                              \t\n"
               :[start]"=&r"(start), [end]"=&r"(end),
                [line_size]"=&r"(line_size), [mask]"=&r"(mask)
               :[code]"r"(code), [size]"r"(size)
       );
}

Over!

Mozilla JavaScript 的 MIPS N32 ABI 支持补丁

Mozilla JavaScript 17.0 已经合并了 MIPS 公司开发的 MIPS 相关 JIT 补丁,仅支持 O32 ABI 的,此补丁用于增加 N32 ABI 的支持。

Mozilla JavaScript 官方版本:http://ftp.mozilla.org/pub/mozilla.org/js/mozjs17.0.0.tar.gz
N32 ABI 支持补丁:http://mirrors.heiher.info/sources/extra/js/

jit-test

python2 ./jit_test.py ../js17                                                                                                         
[4876|   0|   0|4876]    100% =======================================>|  466.9s
PASSED ALL

v8 benchmark

../js17 -m -n -f run.js
Richards: 838
DeltaBlue: 1063
Crypto: 1098
RayTrace: 330
EarleyBoyer: 991
RegExp: 128
Splay: 1182
NavierStokes: 732
----
Score (version 7): 659

Over!

提升 QEMU 中 MIPS-Malta 机器的 RAM 容量上限

目前 QEMU 中 MIPS-Malta 机器的 RAM 容量被限制在了最高 256M,原因是其外围设备被映射在了物理地址空间的 0x10000000 – 0x20000000 之间。为了使用更多的 RAM,需要同时修改机器的“硬件”配置和内核中的地址空间映射表来使用高端内存。为此需要使用 MIPS-5Kf 处理器且需要使用64位的内核,因为要访问 0x20000000 之后的物理地址空间。

在 QEMU 中,当 RAM 配置 <= 256M 时,只使用低 256M物理地址空间来映射;当 RAM 配置 > 256M 时,将其余部分映射到 0x20000000 之后,配置为:

0x00000000 - 0x0fffffff => RAM
0x10000000 - 0x1fffffff => 设备
0x20000000 -            => RAM

在 内核 中,增加一个内存映射表项,来标记 0x20000000 – 0x? 为 BOOT_MEM_RAM。

QEMU 补丁 => https://github.com/heiher/hev-patches/blob/master/qemu/qemu-mips-malta.diff
Kernel 补丁 => https://github.com/heiher/hev-patches/blob/master/linux/mips-malta.diff
Kernel 配置 => http://heiher.info/sftp/files/config.malta

目前,最高可设置为 2047M 的 RAM。

Over!

udevd worker timeout

Error information

Waiting for UDev uevents to be processed    [BUSY]
udevd[155]: worker [173] timeout, kill it
udevd[155]: seq 1431 '/devices/pci0000:00/0000:00:1c.1/0000:12:00.0' killed
udevd[155]: worker [173] terminated by signal 9 (Killed)

PCI device

12:00.0 Network controller: Broadcom Corporation BCM4313 802.11b/g/n Wireless LAN Controller (rev 01)

How to fix?
Edit /etc/rc.conf

MODULES=(fuse brcmsmac)  # add brcmsmac

Over!

Python for MIPS 修正记录

最近发现 Python2 和 Python3 在 import 共享库形式的模块时经常出现 “invalid mode parameter” 错误,追踪后发现是由于 MIPS 平台中 RTLD_GLOBAL 等宏和 x86 平台中定义值是不同的,而 Python 源代码中是默认为 x86 平台生成的,所以在编译前需要重新生成。

重新生成方法
Python2

cd Lib/plat-linux2/
./regen

Python3

cd Lib/plat-linux2/
./regen
cd Lib/plat-linux3/
./regen

Over!

Linux 输入设备共享程序: RInput

是否在调试中因频繁切换 Target 和 Host 的输入设备而感觉头大?是否因为 Target 主机的键盘不适应还感到不爽?RInput
帮你解决这个问题,它可以通过定义的一个按键来将 Host 主机的输入设备在两个主机间切换。

How to Build
Linux:

git clone --recursive https://gitlab.com/hev/hev-rinput
cd hev-rinput
make

Android:

mkdir hev-rinput
cd hev-rinput
git clone --recursive https://gitlab.com/hev/hev-rinput jni
ndk-build

How to Use
Receiver:

cat conf/main.ini
[Main]
Port=6380
Address=0.0.0.0
 
sudo bin/hev-rinput conf/main.ini

Sender:

cat conf/main.ini
[Main]
Port=6380
Address=192.168.1.2
; See /usr/include/linux/input-event-codes.h
SwitchKeyCode=119
 
sudo bin/hev-rinput conf/main.ini

默认 Switch Key
Pause

Over!