在带有裸机组件的RP2040上启动核心1



据我了解文档 2.8.2,启动核心 1 的过程是通过 FIFO 发送一系列值,最后 3 个是向量表、堆栈指针和入口点,而核心 1 会将值回显给你。

从文档提供的 c 代码中,我写出了这个程序集:

.cpu cortex-m0
.thumb
ent:
ldr r0, =0x20001000
mov sp, r0              @init stack pointer
ldr r0, =0xe000ed08
ldr r3, [r0]            @vector table offset register
core:
mov r7, pc
b fifo_drain
sev
mov r1, #0
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, #0
bne core
mov r7, pc
b fifo_drain
sev
mov r1, #0
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, #0
bne core
mov r1, #1
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, #1
bne core
mov r1, r3              @vector table
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, r3
bne core
mov r1, sp              @stack pointer
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, sp
bne core
mov r1, pc
add r1, #2              @entry point
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
ldr r0, =0xd0000000
ldr r1, [r0]
cmp r1, #1
beq led

通过 FIFO 发送的值序列为 {0, 0, 1, vt, sp, ent},当值未回显时,序列将重新开始。入口点只是最后 4 行,内核从 SIO 读取 CPUID 寄存器,如果 CPU ID 为 1,则打开 LED (GPIO25)。

该序列似乎卡在向量表的循环中,这是有道理的,因为我几乎不理解它,FIFO 只是没有回显它。此外,文档在入口点旁边有一个注释,上面写着"不要忘记拇指位!",无论这意味着什么。

编辑:

更新的代码,同样的问题:

.cpu cortex-m0
.thumb
ent:
ldr r0, =0x20001000
mov sp, r0              @init stack pointer
ldr r0, =0xe000ed08
ldr r1, =0x20000000
str r1, [r0]            @init vtor
ldr r0, =0xd0000000
ldr r1, [r0]
cmp r1, #1
beq led

b core

.thumb_func
core:
mov r7, pc
b fifo_drain
mov r1, #0
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, #0
bne core

mov r7, pc
b fifo_drain
mov r1, #0
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, #0
bne core

mov r1, #1
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, #1
bne core

ldr r3, =0x20000000
mov r1, r3              @vector table
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, r3
bne core

mov r1, sp              @stack pointer
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, sp
bne core

ldr r3, =0x20000001
mov r1, r3              @entry point
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, r3
bne core

b loop

.thumb_func
fifo_stat:
ldr r0, =0xd0000050
ldr r1, [r0]
mov r2, #15
and r1, r1, r2
mov pc, r7
.thumb_func
fifo_writ:
ldr r0, =0xd0000050
ldr r3, [r0]
mov r2, #2
and r3, r3, r2
beq fifo_writ

ldr r0, =0xd0000054
str r1, [r0]
sev
mov pc, r7
.thumb_func
fifo_read:
ldr r0, =0xd0000050
ldr r3, [r0]
mov r2, #1
and r3, r3, r2
beq _wfe
ldr r0, =0xd0000058
ldr r1, [r0]
mov pc, r7
.thumb_func
fifo_drain:
ldr r0, =0xd0000058
ldr r1, [r0]
ldr r0, =0xd0000050
ldr r1, [r0]
mov r2, #1
and r1, r1, r2
bne fifo_drain
sev
mov pc, r7

.thumb_func
_wfe:
wfe
b fifo_read
.thumb_func
led:
movs r1, #32            @io_bank
ldr r0, =0x4000f000
str r1, [r0]            @release reset on io_bank
movs r1, #5             @sio
ldr r0, =0x400140cc
str r1, [r0]            @assign sio to gpio25_ctrl
movs r1, #1
lsl r1, r1, #25

ldr r0, =0xd0000024
str r1, [r0]            @enable output
ldr r0, =0xd0000014
str r1, [r0]            @turn on the led

.thumb_func
loop:
nop
b loop

我的核心零代码是C和汇编语言的混合体。 我想我们可以解决你的问题。

我的引导程序看起来像这样

.cpu cortex-m0
.thumb
ldr r1,=0xD0000000 ;@SIO_CPUID
ldr r0,[r1]
cmp r0,#0
bne core_one
;@ core_zero
ldr r0,=0x20002000
mov sp,r0
bl zero_entry
b .
core_one:
;@ core_one
bl notmain
b .
.align
.ltorg

;@ ----------------------------------
.balign 0x100
.thumb_func
.globl PUT32
PUT32:
str r1,[r0]
bx lr
.thumb_func
.globl GET32
GET32:
ldr r0,[r0]
bx lr
.globl SEV
.thumb_func
SEV:
sev
bx lr
.globl WFE
.thumb_func
WFE:
wfe
bx lr
.globl DELAY
.thumb_func
DELAY:
sub r0,#1
bne DELAY
bx lr

我链接到0x20000000并为sram/0x20000000构建我的uf2文件作为二进制文件的目的地。这取决于具体情况,但您需要知道代码的运行位置。

我的核心零代码看起来像这样

extern void PUT32 ( unsigned int, unsigned int );
extern unsigned int GET32 ( unsigned int );
extern void SEV ( void );
extern void WFE ( void );
#define SIO_BASE                    0xD0000000
#define SIO_FIFO_ST                 (SIO_BASE+0x50)
#define SIO_FIFO_WR                 (SIO_BASE+0x54)
#define SIO_FIFO_RD                 (SIO_BASE+0x58)
static void fifo_flush ( void )
{
while(1)
{
if((GET32(SIO_FIFO_ST)&0x1) == 0) break; //zero if empty
GET32(SIO_FIFO_RD);
}
SEV();
}
static unsigned int fifo_send ( unsigned int cmd )
{
while(1)
{
if((GET32(SIO_FIFO_ST)&0x2) != 0) break; //one if ready
}
PUT32(SIO_FIFO_WR,cmd);
SEV();
while(1)
{
if((GET32(SIO_FIFO_ST)&0x1) == 0) //zero if  empty
{
WFE();
}
else
{
break;
}
}
return(GET32(SIO_FIFO_RD));
}
unsigned int zero_entry ( void )
{
unsigned int ra;
while(1)
{
fifo_flush();
ra=fifo_send(0);
if(ra!=0) continue;
fifo_flush();
ra=fifo_send(0);
if(ra!=0) continue;
ra=fifo_send(1);
if(ra!=1) continue;
ra=fifo_send(0x20000000); //vector_table
if(ra!=0x20000000) continue;
ra=fifo_send(0x20003000);    //stack pointer
if(ra!=0x20003000) continue;
ra=fifo_send(0x20000001);    //entry
if(ra!=0x20000001) continue;
break;
}
return(0);
}

如果有兴趣,我的核心代码看起来像这样

void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void DELAY ( unsigned int );
#define RESETS_BASE                 0x4000C000
#define RESETS_RESET_RW             (RESETS_BASE+0x0+0x0000)
#define RESETS_RESET_XOR            (RESETS_BASE+0x0+0x1000)
#define RESETS_RESET_SET            (RESETS_BASE+0x0+0x2000)
#define RESETS_RESET_CLR            (RESETS_BASE+0x0+0x3000)
#define RESETS_WDSEL_RW             (RESETS_BASE+0x4+0x0000)
#define RESETS_WDSEL_XOR            (RESETS_BASE+0x4+0x1000)
#define RESETS_WDSEL_SET            (RESETS_BASE+0x4+0x2000)
#define RESETS_WDSEL_CLR            (RESETS_BASE+0x4+0x3000)
#define RESETS_RESET_DONE_RW        (RESETS_BASE+0x8+0x0000)
#define RESETS_RESET_DONE_XOR       (RESETS_BASE+0x8+0x1000)
#define RESETS_RESET_DONE_SET       (RESETS_BASE+0x8+0x2000)
#define RESETS_RESET_DONE_CLR       (RESETS_BASE+0x8+0x3000)
#define SIO_BASE                    0xD0000000
#define SIO_GPIO_OUT_RW             (SIO_BASE+0x10)
#define SIO_GPIO_OUT_SET            (SIO_BASE+0x14)
#define SIO_GPIO_OUT_CLR            (SIO_BASE+0x18)
#define SIO_GPIO_OUT_XOR            (SIO_BASE+0x1C)
#define SIO_GPIO_OE_RW              (SIO_BASE+0x20)
#define SIO_GPIO_OE_SET             (SIO_BASE+0x24)
#define SIO_GPIO_OE_CLR             (SIO_BASE+0x28)
#define SIO_GPIO_OE_XOR             (SIO_BASE+0x2C)
#define IO_BANK0_BASE               0x40014000
#define IO_BANK0_GPIO25_STATUS_RW   (IO_BANK0_BASE+0x0C8+0x0000)
#define IO_BANK0_GPIO25_STATUS_XOR  (IO_BANK0_BASE+0x0C8+0x1000)
#define IO_BANK0_GPIO25_STATUS_SET  (IO_BANK0_BASE+0x0C8+0x2000)
#define IO_BANK0_GPIO25_STATUS_CLR  (IO_BANK0_BASE+0x0C8+0x3000)
#define IO_BANK0_GPIO25_CTRL_RW     (IO_BANK0_BASE+0x0CC+0x0000)
#define IO_BANK0_GPIO25_CTRL_XOR    (IO_BANK0_BASE+0x0CC+0x1000)
#define IO_BANK0_GPIO25_CTRL_SET    (IO_BANK0_BASE+0x0CC+0x2000)
#define IO_BANK0_GPIO25_CTRL_CLR    (IO_BANK0_BASE+0x0CC+0x3000)
int notmain ( void )
{
//release reset on IO_BANK0
PUT32(RESETS_RESET_CLR,1<<5); //IO_BANK0
//wait for reset to be done
while(1)
{
if((GET32(RESETS_RESET_DONE_RW)&(1<<5))!=0) break;
}
//output disable
PUT32(SIO_GPIO_OE_CLR,1<<25);
//turn off pin 25
PUT32(SIO_GPIO_OUT_CLR,1<<25);
//set the function select to SIO (software controlled I/O)
PUT32(IO_BANK0_GPIO25_CTRL_RW,5);
//output enable
PUT32(SIO_GPIO_OE_SET,1<<25);
while(1)
{
//turn on the led
PUT32(SIO_GPIO_OUT_SET,1<<25);
DELAY(0x100000);
//turn off the led
PUT32(SIO_GPIO_OUT_CLR,1<<25);
DELAY(0x100000);
}
return(0);
}

拇指位是什么意思? 如果您查看 ARM 文档(armv6-m 体系结构参考手册)中的 bx 指令或其他相关信息。 这可以追溯到可以运行手臂和拇指代码的全尺寸内核。 由于两种模式下的指令都是对齐的,因此他们选择将lsbit用于按地址分支的指令来确定在分支目的地使用的模式(最初只有bx指令,但后来是pop和其他指令)。 如果设置了 lsbit,则它分支到拇指指令,如果重置,则分支到 arm 指令。

他们选择的cortex-ms使用向量表(根据产品的目标市场有意义),而不是像以前的全尺寸内核(ARM7,ARM9,ARM10,ARM11)那样的硬编码地址。 如体系结构参考手册中所述,第一个单词是放入堆栈指针中的值,用于在引导过程中保存该步骤,第二个单词是重置向量。

现在ARM选择这样做,你必须在那里放置一个拇指函数指针地址,这意味着lsbit是ORR的。我强调 ORRed 用一个而不是添加一个,因为如果您正确使用工具(IMO),那么该工具将设置 lsbit 和 ADDing,然后您将破坏它。

让工具完成工作

.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
.word 0x20001000
.word reset
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang:   b .

(这在 pico 上不起作用,这是一个拇指是什么意思)。

.thumb_func会导致它在代码中找到的下一个标签是一个拇指函数地址,而不仅仅是一个普通的旧地址。

所以这给了

00200000 <_start>:
200000:   20001000    andcs   r1, r0, r0
200004:   00200041    eoreq   r0, r0, r1, asr #32
200008:   00200047    eoreq   r0, r0, r7, asr #32
20000c:   00200047    eoreq   r0, r0, r7, asr #32
200010:   00200047    eoreq   r0, r0, r7, asr #32
200014:   00200047    eoreq   r0, r0, r7, asr #32
200018:   00200047    eoreq   r0, r0, r7, asr #32
20001c:   00200047    eoreq   r0, r0, r7, asr #32
200020:   00200047    eoreq   r0, r0, r7, asr #32
200024:   00200047    eoreq   r0, r0, r7, asr #32
200028:   00200047    eoreq   r0, r0, r7, asr #32
20002c:   00200047    eoreq   r0, r0, r7, asr #32
200030:   00200047    eoreq   r0, r0, r7, asr #32
200034:   00200047    eoreq   r0, r0, r7, asr #32
200038:   00200047    eoreq   r0, r0, r7, asr #32
20003c:   00200047    eoreq   r0, r0, r7, asr #32
00200040 <reset>:
200040:   f000 f81a   bl  200078 <notmain>
200044:   e7ff        b.n 200046 <hang>
00200046 <hang>:
200046:   e7fe        b.n 200046 <hang>

为不同的 MCU(而不是 PCI)构建和链接。 重置在0x00200040时挂起,在0x00200046挂起。 这些工具已经为我们完成了工作,因为我们使用了.thumb_func,并将地址放在一个地址上。

一切都很高兴,这个 mcu 会启动,或者至少重置后不会立即挂起。

更长的方法,没有.arm_func所以对于ARM和拇指,你可以做

.type reset,%function
reset:

它不必紧挨在标签之前,但您必须做额外的工作来键入标签名称。

如果我拿你的代码并像这样改变它:

ldr r1, =one_entry
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
.thumb_func
one_entry:
ldr r0, =0xd0000000
ldr r1, [r0]
cmp r1, #1
beq led

然后我得到

2000005a:   4907        ldr r1, [pc, #28]   ; (20000078 <one_entry+0x14>)
2000005c:   467f        mov r7, pc
2000005e:   e011        b.n 20000084 <fifo_writ>
20000060:   467f        mov r7, pc
20000062:   e00e        b.n 20000082 <fifo_read>
20000064 <one_entry>:
20000064:   4805        ldr r0, [pc, #20]   ; (2000007c <one_entry+0x18>)
20000066:   6801        ldr r1, [r0, #0]
20000068:   2901        cmp r1, #1
2000006a:   d00c        beq.n   20000086 <led>
2000006c:   e7fe        b.n 2000006c <one_entry+0x8>
2000006e:   46c0        nop         ; (mov r8, r8)
20000070:   20001000    andcs   r1, r0, r0
20000074:   e000ed08    and lr, r0, r8, lsl #26
20000078:   20000065    andcs   r0, r0, r5, rrx
2000007c:   d0000000    andle   r0, r0, r0

该工具已创建指向设置了 lsbit 的内核 1 的入口点的地址。 20000065

现在你遇到的下一个问题是

mov r1, sp              @stack pointer

此时,您将在核心零执行中获取核心零堆栈指针地址,并为核心零设置该地址。 如果您在启动核心 1 后无限循环中结束核心零,那么这可以工作。但是如果你想继续用核心零做事,你需要给核心一自己的堆栈指针。 在我的示例中,您可以看到我给核心零0x20002000,核心一0x20003000。 调试起来会非常痛苦,因为核心会启动,但是每次更改代码时都会发生随机混乱。

以及您的 VTOR 问题。 我也尝试只阅读 VTOR,但它不起作用。 最初我的代码有一个特殊的向量表:

.globl vector_table
vector_table:
b reset
.balign 4
.word reset ;@ has to be offset 4
.word loop
.word loop
.word loop

我设置了向量表,而不是读取它

ldr r1,=0xE000ED08 ;@ VTOR
ldr r0,=vector_table
str r0,[r1]

对于核心零,这可能是从我编写的其他可能实际使用该表的 pico 代码中借来的。 b重置,因为我们实际上没有将重置向量用于核心零,所以这是我的难题。本可以完成对齐工作并将向量表放在内存中的其他地方(是的,对于两个内核,我最初都自己设置了堆栈指针,但对于上面的示例,假设内核是自己做的)。

并将相同的地址vector_table用于核心一。 在这种情况下,我可以阅读它,它会起作用。 您只提供了一小部分,因此我们不知道在此代码之前您对核心零的 VTOR 做了什么,但我假设您没有设置它,因为您的代码不起作用。

您/我们在这些示例中没有使用向量表,所以只需要让它开心,所以我只是强制0x20000000然后它起作用了。

我相信您需要修复所有三个地址,向量表,入口点和堆栈指针才能成功。


从您的重写中,我进行了这些修改。

.cpu cortex-m0
.thumb
ent:
ldr r0, =0x20001000
mov sp, r0              @init stack pointer
ldr r0, =0xe000ed08
ldr r1, =0x20000000
str r1, [r0]            @init vtor
ldr r0, =0xd0000000
ldr r1, [r0]
cmp r1, #1
beq led
b core
.thumb_func
core:
mov r7, pc
b fifo_drain
mov r1, #0
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, #0
bne core
mov r7, pc
b fifo_drain
mov r1, #0
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, #0
bne core
mov r1, #1
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, #1
bne core
ldr r4, =0x20000000
mov r1, r4              @vector table
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, r4
bne core
mov r4, sp              @stack pointer
mov r1, r4
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, r4
bne core
ldr r4, =0x20000001
mov r1, r4              @entry point
mov r7, pc
b fifo_writ
mov r7, pc
b fifo_read
cmp r1, r4
bne core
b loop
.thumb_func
fifo_stat:
ldr r0, =0xd0000050
ldr r1, [r0]
mov r2, #15
and r1, r1, r2
mov pc, r7
.thumb_func
fifo_writ:
ldr r0, =0xd0000050
ldr r3, [r0]
mov r2, #2
and r3, r3, r2
beq fifo_writ
ldr r0, =0xd0000054
str r1, [r0]
sev
mov pc, r7
.thumb_func
fifo_read:
ldr r0, =0xd0000050
ldr r3, [r0]
mov r2, #1
and r3, r3, r2
beq _wfe
ldr r0, =0xd0000058
ldr r1, [r0]
mov pc, r7
.thumb_func
fifo_drain:
ldr r0, =0xd0000058
ldr r1, [r0]
ldr r0, =0xd0000050
ldr r1, [r0]
mov r2, #1
and r1, r1, r2
bne fifo_drain
sev
mov pc, r7
.thumb_func
_wfe:
wfe
b fifo_read
;@ ----------------------------------
.balign 0x100
.thumb_func
led:
movs r1, #32            @io_bank
ldr r0, =0x4000f000
str r1, [r0]            @release reset on io_bank
movs r1, #5             @sio
ldr r0, =0x400140cc
str r1, [r0]            @assign sio to gpio25_ctrl
movs r1, #1
lsl r1, r1, #25
ldr r0, =0xd0000024
str r1, [r0]            @enable output
ldr r0, =0xd0000014
str r1, [r0]            @turn on the led
.thumb_func
loop:
nop
b loop

首先,在几个地方,您使用 r3 来保存要在写入和回读后进行比较的值。 但是 r3 在写入和读取中都使用,因此其内容会丢失。

其次,程序大于 0x100 字节,有些奇怪,我必须了解我是如何弄清楚的,所以通过避免边界然后它起作用了。

如上所述,sp不需要转到r4,但是我这样做是为了解决问题。

如果我删除不需要的项目(写入 VTOR,前面有一个 b 核心。 我使用 bl 和 bx lr 来调用和返回,这节省了足够的指令来使二进制小于 0x100 个字节。 它可以在不设置边界的情况下使用。

.cpu cortex-m0
.thumb
ent:
ldr r0, =0x20001000
mov sp, r0              @init stack pointer
ldr r0, =0xd0000000
ldr r1, [r0]
cmp r1, #1
beq led
core:
bl fifo_drain
mov r1, #0
bl fifo_writ
bl fifo_read
cmp r1, #0
bne core
b fifo_drain
mov r1, #0
bl fifo_writ
bl fifo_read
cmp r1, #0
bne core
mov r1, #1
bl fifo_writ
bl fifo_read
cmp r1, #1
bne core
ldr r4, =0x20000000
mov r1, r4              @vector table
bl fifo_writ
bl fifo_read
cmp r1, r4
bne core
mov r1, sp              @stack pointer
bl fifo_writ
bl fifo_read
cmp r1, sp
bne core
ldr r4, =0x20000001
mov r1, r4              @entry point
bl fifo_writ
bl fifo_read
cmp r1, r4
bne core
b loop
fifo_stat:
ldr r0, =0xd0000050
ldr r1, [r0]
mov r2, #15
and r1, r1, r2
bx lr
fifo_writ:
ldr r0, =0xd0000050
ldr r3, [r0]
mov r2, #2
and r3, r3, r2
beq fifo_writ
ldr r0, =0xd0000054
str r1, [r0]
sev
bx lr
fifo_read:
ldr r0, =0xd0000050
ldr r3, [r0]
mov r2, #1
and r3, r3, r2
beq _wfe
ldr r0, =0xd0000058
ldr r1, [r0]
bx lr
fifo_drain:
ldr r0, =0xd0000058
ldr r1, [r0]
ldr r0, =0xd0000050
ldr r1, [r0]
mov r2, #1
and r1, r1, r2
bne fifo_drain
sev
bx lr
_wfe:
wfe
bl fifo_read
led:
movs r1, #32            @io_bank
ldr r0, =0x4000f000
str r1, [r0]            @release reset on io_bank
movs r1, #5             @sio
ldr r0, =0x400140cc
str r1, [r0]            @assign sio to gpio25_ctrl
movs r1, #1
lsl r1, r1, #25
ldr r0, =0xd0000024
str r1, [r0]            @enable output
ldr r0, =0xd0000014
str r1, [r0]            @turn on the led
loop:
nop
b loop

请注意,指令集允许这样的事情:

fifo_drain:
ldr r0, =0xd0000050
ldr r1, [r0,#8] @0xd0000058
ldr r1, [r0] @0xd0000050
mov r2, #1
and r1, r1, r2
bne fifo_drain
sev
bx lr

不像蛮力和简单易读,但节省了指令。

对于刚刚学习ARM汇编语言的人来说,我认为同时是rp2040。我印象很深刻,继续努力。 这个特殊的MCU非常非常酷,但记录也很差。 ARM指令集有据可查,但是使用ARM与拇指,然后统一语法与非(幸运的是,您没有遇到差异)。 这个0x100字节的事情,我不记得我是怎么想出来的,我想我看了他们的代码并从中找出来,但我必须重新研究整个事情。 如果您想自己确认这一点,请使用一个小于 0x100 字节的版本,然后在正文中的某个位置添加一些 nops 以将其拉伸超过 0x100 个字节。 请注意描述的简单更改并删除未使用/需要的代码,我得到了您的

216 bytes read (0xD8)

216 字节...

底线。

您对这三个参数有正确的想法,但它们需要一些工作。 然后一个简单的嘎嘎在函数调用中使用的函数调用之外使用寄存器。 然后是疯狂的0x100字节的事情。 这就是裸机的东西,很难调试,必须磨练你的方式,不要放弃。

mov r7,pc 的事情,我实际上印象深刻,而不是批评 - 很多人会在前面的两个指令中挣扎。

相关内容

  • 没有找到相关文章

最新更新