爬Elbrus-侦察战。 技术部分1.寄存器,堆栈和其他技术细节

按照承诺 ,我们将继续谈论Elbrus处理器的开发。 本文是技术性的。 本文中提供的信息不是官方文档,因为它是在研究Elbrus时获得的,就像黑盒子一样。 但是,对于更好地理解Elbrus架构肯定会很有趣,因为尽管我们拥有官方文档,但是许多细节只有经过漫长的实验( Embox投入使用)后才变得清晰。

回想一下,在上一篇文章中,我们讨论了基本的系统启动和串行端口驱动程序。 Embox开始了,但是为了进一步发展,我们需要中断,系统计时器,当然,我想包括一些单元测试,为此,我们需要setjmp。 本文将重点介绍实现所有这些内容所需的寄存器,堆栈和其他技术细节。

让我们首先简要介绍一下体系结构,这是理解后面将要讨论的内容所需的最少信息。 将来,我们将参考本节中的信息。

简介:堆栈


Elbrus中有三叠:

  • 程序栈(PS)
  • 程序链堆栈(PCS)
  • 用户堆栈(美国)

让我们更详细地分析它们。 图中的地址是有条件的,显示运动的方向-从较大的地址到较小的地址,反之亦然。



过程堆栈(PS)用于分配给“操作”寄存器的数据。

例如,它可能是函数参数;在“普通”体系结构中,该概念最接近通用寄存器。 与“常规”处理器体系结构不同,在E2K中,功能中使用的寄存器被堆叠在单独的堆栈中。

绑定信息堆栈(PCS)旨在放置有关先前(调用)过程的信息,并在返回时使用。 返回地址上的数据以及寄存器中的数据都放在单独的位置。 因此,堆栈升级(例如,在C ++中异常退出)比“普通”体系结构耗时更多。 另一方面,这消除了堆栈溢出问题。

这两个堆栈(PS和PCS)的特征均在于基地址,大小和当前偏移量。 这些参数是在PSP和PCSP寄存器中设置的,它们是128位的,在汇编程序中,您需要引用特定的字段(例如,高或低)。 此外,堆栈的功能与寄存器文件的概念密切相关,更多内容请参见下文。 与文件的交互通过泵浦/交换寄存器的机制发生。 在此机制中,绑定信息的过程或堆栈的所谓“指向堆栈顶部的硬件指针”分别扮演着积极的角色。 关于它也在下面。 重要的是,在每个时间点,这些堆栈的数据都位于RAM或寄存器文件中。

还值得注意的是,这些堆栈(过程堆栈和绑定信息堆栈)长大 。 当我们实现context_switch时,我们遇到了这个问题。

还为用户堆栈提供了基地址和大小。 当前指针在寄存器USD.lo中。 从本质上讲,它是一个成长中的经典堆栈。 只是,与“普通”架构不同,其他堆栈(寄存器和返回地址)中的信息不适合此处。

在我看来,对堆栈的边界和大小的一种非标准要求是4Kb对齐,并且堆栈的基址及其大小都应与4Kb对齐。 在其他体系结构中,我没有遇到这样的限制。 当我们实现context_switch时,我们又遇到了这个细节。

简介:寄存器。 注册文件。 注册窗口


现在我们已经弄清楚了堆栈,我们需要了解如何在堆栈中显示信息。 为此,我们需要引入更多概念。

寄存器文件(RF)是所有寄存器的集合。 我们需要两个寄存器文件:一个连接信息文件(链文件-CF),另一个称为寄存器文件(RF),它存储“操作”寄存器,这些寄存器存储在过程堆栈中。

寄存器窗口是当前可用的寄存器文件的区域(寄存器集)。

我将更详细地解释。 我认为什么是寄存器集,没有人需要解释。

众所周知,x86体系结构的瓶颈之一就是寄存器数量很少。 在带有寄存器的RISC体系结构中,它比较简单,通常约为16个寄存器,其中有几个(2-3)用于官方需求。 为什么不仅仅做128个寄存器,因为这似乎会提高系统性能? 答案很简单:处理器指令需要一个位置来存储寄存器地址,并且如果有很多,那么还需要很多位。 因此,他们会花各种技巧,制作影子寄存器,寄存器组,窗口等。 影子寄存器是指ARM中寄存器组织的原理。 如果发生中断或其他情况,则可以使用具有相同名称(编号)的另一组寄存器,而存储在原始组中的信息保留在那里。 实际上,寄存器组与影子寄存器非常相似,寄存器组没有硬件切换,程序员选择现在要联系哪个组(寄存器组)。

寄存器窗口旨在优化堆栈工作。 如您可能理解的那样,在“常规”体系结构中,您可以输入一个过程,将寄存器保存到堆栈中(或保存调用过程,取决于协议),并且可以使用寄存器,因为信息已存储在堆栈中。 但是内存访问速度很慢,因此应避免使用。 进入此过程时,我们只需提供一组新的寄存器,旧寄存器中的数据将被保存,这意味着您无需将其转储到内存中。 此外,当您返回调用过程时,先前的寄存器窗口也会返回,因此,寄存器上的所有数据都将是相关的。 这是寄存器窗口的概念。



显然,您仍然需要将寄存器保存在堆栈中(在内存中),但是当可用寄存器窗口结束时,可以这样做。

以及如何处理输入和输出寄存器(输入函数和返回结果时的参数)? 让该窗口包含从上一个窗口可见的部分寄存器,更确切地说,部分寄存器将可用于两个窗口。 然后,通常,在调用函数时,您不必访问内存。 假设我们的寄存器看起来像这样



也就是说,第一个窗口中的r0与零中的r2是相同的寄存器,而第一个窗口中的r1与r3在相同的寄存器中。 也就是说,在调用过程(更改窗口号)之前写入r2,我们在被调用过程中获得r0中的值。 此原理称为旋转窗口的机制。

让我们进一步优化,因为Elbrus的创建者正是这样做的。 让我们拥有的窗口不是固定大小,而是可变的,可以在进入程序时设置窗口大小。 我们将对旋转寄存器的数量执行相同的操作。 当然,这将导致我们遇到一些问题,因为如果在经典的可旋转窗口中,存在一个窗口索引,则可以通过该索引确定您需要将寄存器文件中的数据保存到堆栈中或进行加载。 但是,如果您输入的不是窗口索引,而是当前窗口从其开始的寄存器索引,则不会出现此问题。 在Elbrus中,这些索引包含在寄存器PSHTP(用于PS过程堆栈)和PCSHTP(用于PCS过程信息堆栈)中。 该文档指的是“指向堆栈顶部的硬件指针”。 现在您可以再次尝试阅读有关堆栈的信息,我认为这会更加清楚。

如您所知,这种机制意味着您具有控制内存中内容的能力。 即,同步寄存器文件和堆栈。 我的意思是系统程序员。 如果您是一名应用程序程序员,则该设备将提供透明的程序进入和退出过程。 也就是说,如果尝试选择新窗口时没有足够的寄存器,则寄存器窗口将自动“弹出”。 在这种情况下,寄存器文件中的所有数据都将保存在适当的堆栈中(在内存中),并且“指向堆栈硬件顶部的指针”(偏移索引)将重置为零。 同样,自动从堆栈中交换寄存器文件。 但是,例如,如果您正在开发上下文切换(这正是我们所做的),则需要一种机制来处理寄存器文件的隐藏部分。 在Elbrus中,为此使用了FLUSHR和FLUSHC命令。 FLUSHR-清除寄存器文件,除当前窗口外的所有窗口都将刷新到程序堆栈,因此PSHTP索引将重置为零。 FLUSHC-清除绑定信息文件,将当前窗口以外的所有内容都转储到绑定信息堆栈中,PCSHTP索引也重置为零。

简介:在Elbrus中实现


现在,我们已经讨论了关于寄存器和堆栈的非显而易见的工作,我们将更具体地讨论Elbrus中的各种情况。

当我们输入下一个函数时,处理器将创建两个窗口:PS堆栈上的一个窗口和PCS堆栈上的一个窗口。

PCS堆栈中的窗口包含从函数返回所必需的信息:例如,需要从函数返回的指令的IP(指令指针)。 这样,一切都差不多了。

PS堆栈上的窗口有点棘手。 介绍了当前窗口寄存器的概念。 在此窗口中,您可以访问当前窗口的寄存器-%dr0,%dr1,...,%dr15,... ...也就是说,对于我们来说,作为用户,它们始终从0开始编号,但这是相对于当前窗口的基址编号的。 通过这些寄存器,在调用函数时将传递参数,并返回值,并将该函数用作函数内部的通用寄存器。 实际上,在考虑旋转寄存器窗口的机制时对此进行了解释。

可以控制Elbrus中注册窗口的大小。 正如我所说,这是优化所必需的。 例如,在一个函数中,我们仅需要4个寄存器来传递参数和进行一些计算,在这种情况下,程序员(或编译器)将决定为该函数分配多少个寄存器,并以此为基础设置窗口大小。 窗口大小由setwd操作设置:

setwd wsz=0x10 

以四寄存器(128位寄存器)为单位指定窗口大小。



现在,假设您要从一个函数中调用一个函数。 为此,应用已经描述的旋转寄存器窗口的概念。 上图显示了寄存器文件的一部分,其中窗口1(绿色)的函数调用窗口2(橙色)的函数。 在这两个函数的每一个中,您都可以访问%dr0,%dr1...。但是参数将通过所谓的旋转寄存器传递。 换句话说,窗口1的部分寄存器将成为窗口2的寄存器(请注意,这两个窗口相交)。 这些寄存器也由窗口设置(请参见图片中的旋转寄存器),并且具有地址%db [0],%db [1],...。因此,窗口2中的%dr0寄存器仅等于窗口中的%db [0]寄存器。视窗1。

旋转寄存器窗口是通过setbn操作设置的:

  setbn rbs = 0x3, rsz = 0x8 

rbs设置旋转窗口的大小,rsz设置基址,但相对于当前寄存器窗口。 即 在这里,我们从8号开始分配了3个寄存器。

基于上述内容,我们展示了函数调用的外观。 为简单起见,我们假定该函数采用一个参数:

 void my_func(uint64_t a) { } 

然后,要调用此函数,您需要准备一个旋转寄存器窗口(我们已经通过setbn完成了此操作)。 接下来,在%db0寄存器中,我们将传递给my_func的值。 此后,您需要调用CALL指令,并且不要忘记告诉她旋转寄存器的窗口在哪里开始。 由于它不区分大小写,因此我们现在跳过该调用的准备工作(disp命令)。 结果,在汇编器中,对此函数的调用应如下所示:

  addd 0, %dr9, %db[0] disp %ctpr1, my_func call %ctpr1, wbs = 0x8 

因此,与寄存器有点想通。 现在,让我们看一下绑定信息的堆栈。 它存储所谓的CR寄存器。 实际上,两个-CR0,CR1。 并且它们已经包含了从函数返回所需的信息。



用橙色标记的寄存器调用该函数的窗口的寄存器CR0和CR1为绿色。 CR0寄存器包含调用函数的指令指针和某个谓词文件(PF谓词文件),有关它的故事绝对不在本文讨论范围之内。

CR1寄存器包含诸如PSR(字处理器状态),窗口编号,窗口大小等数据。 在Elbrus中,一切都非常灵活,每个过程都将信息存储在CR1中,即使该过程中是否包含浮点运算,也包含一个有关软件异常信息的寄存器,但是为此,您当然必须为保存其他信息付费。

非常重要的一点是不要忘记寄存器文件和绑定信息文件可以被抽出并交换出主存储器,反之亦然(从上述PS和PCS堆栈)。 这一点在实现稍后描述的setjmp时很重要。

SETJMP / LONGJMP


最后,至少在某种程度上理解了Elbrus中堆栈和寄存器的排列方式之后,您可以开始做一些有用的事情,即为Embox添加新功能。

在Embox中,单元测试系统需要setjmp / longjmp,因此我们必须实现这些功能。

对于实现,需要保存/恢复以下寄存器:CR0,CR1,PSP,PCSP,USD,这是我们从简短介绍中就已经熟悉的。 实际上,保存/恢复是在我们的额头上实现的,但是在堆栈和寄存器的描述中通常会暗示一个重要的细微差别,即:堆栈应该同步,因为它们不仅位于内存中,而且位于寄存器文件中。 这种细微差别意味着您需要照顾好几个功能,没有这些功能将无法正常工作。

第一个功能是在保存和恢复期间禁用中断。 恢复中断时,必须禁止该中断,否则可能会导致我们进入带有半交换堆栈的中断处理程序(请参阅“简短说明”中所述的抽出寄存器文件交换)。 而且在保存时,问题在于进入和退出中断后,处理器可以再次从RAM交换部分寄存器文件(这将破坏不变条件PSHTP = 0和PSCHTP = 0,有关它们的更多信息)。 这就是为什么在setjmp和longjmp中都必须禁用中断的原因。 这里还应该指出的是,MCST的专家建议我们使用原子括号而不是禁用中断,但是目前我们正在使用最简单的(对我们来说是可以理解的)实现。

第二个特征与从内存中抽/存寄存器文件有关。 如下。 寄存器文件的大小有限,因此经常被泵送到内存中,反之亦然。 因此,如果仅保存PSP和PSHTP寄存器的值,则将当前指针的值固定在内存和寄存器文件中。 但是由于寄存器文件正在更改,因此在上下文还原时,它将指示已经不正确的数据(不是我们“保存”的数据)。 为了避免这种情况,您需要将整个寄存器文件刷新到内存中。 因此,在保存setjmp时,我们在内存中有PSP.ind寄存器,在寄存器窗口中有PSHTP.ind寄存器。 原来,您需要保存整个PCSP.ind + PCSHTP.ind寄存器。 以下是执行此操作的功能:

 /* First arg is PCSP, 2nd arg is PCSHTP * Returns new PCSP value with updated PCSP.ind */ .type update_pcsp_ind,@function $update_pcsp_ind: setwd wsz = 0x4, nfx = 0x0 /* Here and below, 10 is size of PCSHTP.ind. Here we * extend the sign of PCSHTP.ind */ shld %dr1, (64 - 10), %dr1 shrd %dr1, (64 - 10), %dr1 /* Finally, PCSP.ind += PCSHTP.ind */ addd %dr1, %dr0, %dr0 E2K_ASM_RETURN 

还必须澄清注释中描述的该代码中的一小点,即,有必要以编程方式扩展PCSHTP.ind索引中的字符,因为该索引可以为负数并存储在其他代码中。 为此,我们首先移至(64位寄存器)左侧的(64-10),移至10位字段,然后移回。

PSP(过程堆栈)也是如此

 /* First arg is PSP, 2nd arg is PSHTP * Returns new PSP value with updated PSP.ind */ .type update_psp_ind,@function $update_psp_ind: setwd wsz = 0x4, nfx = 0x0 /* Here and below, 12 is size of PSHTP.ind. Here we * extend the sign of PSHTP.ind as stated in documentation */ shld %dr1, (64 - 12), %dr1 shrd %dr1, (64 - 12), %dr1 muld %dr1, 2, %dr1 /* Finally, PSP.ind += PSHTP.ind */ addd %dr1, %dr0, %dr0 E2K_ASM_RETURN 

稍有不同(该字段为12位,并且寄存器以128位计,即该值必须乘以2)。

Setjmp代码本身

 C_ENTRY(setjmp): setwd wsz = 0x14, nfx = 0x0 /* It's for db[N] registers */ setbn rsz = 0x3, rbs = 0x10, rcur = 0x0 /* We must disable interrupts here */ disp %ctpr1, ipl_save ipd 3 call %ctpr1, wbs = 0x10 /* Store current IPL to dr9 */ addd 0, %db[0], %dr9 /* Store some registers to jmp_buf */ rrd %cr0.hi, %dr1 rrd %cr1.lo, %dr2 rrd %cr1.hi, %dr3 rrd %usd.lo, %dr4 rrd %usd.hi, %dr5 /* Prepare RF stack to flush in longjmp */ rrd %psp.hi, %dr6 rrd %pshtp, %dr7 addd 0, %dr6, %db[0] addd 0, %dr7, %db[1] disp %ctpr1, update_psp_ind ipd 3 call %ctpr1, wbs = 0x10 addd 0, %db[0], %dr6 /* Prepare CF stack to flush in longjmp */ rrd %pcsp.hi, %dr7 rrd %pcshtp, %dr8 addd 0, %dr7, %db[0] addd 0, %dr8, %db[1] disp %ctpr1, update_pcsp_ind ipd 3 call %ctpr1, wbs = 0x10 addd 0, %db[0], %dr7 std %dr1, [%dr0 + E2K_JMBBUFF_CR0_HI] std %dr2, [%dr0 + E2K_JMBBUFF_CR1_LO] std %dr3, [%dr0 + E2K_JMBBUFF_CR1_HI] std %dr4, [%dr0 + E2K_JMBBUFF_USD_LO] std %dr5, [%dr0 + E2K_JMBBUFF_USD_HI] std %dr6, [%dr0 + E2K_JMBBUFF_PSP_HI] std %dr7, [%dr0 + E2K_JMBBUFF_PCSP_HI] /* Enable interrupts */ addd 0, %dr9, %db[0] disp %ctpr1, ipl_restore ipd 3 call %ctpr1, wbs = 0x10 /* return 0 */ adds 0, 0, %r0 E2K_ASM_RETURN 

在实现longjmp时,重要的是不要忘记两个寄存器文件的同步,因此,不仅需要刷新寄存器窗口(flushr),还需要刷新绑定文件(flushc)。 让我们描述一下宏:

 #define E2K_ASM_FLUSH_CPU \ flushr; \ nop 2; \ flushc; \ nop 3; 

现在所有信息都已存储在内存中,我们可以安全地在longjmp中进行注册恢复了。

 C_ENTRY(longjmp): setwd wsz = 0x14, nfx = 0x0 setbn rsz = 0x3, rbs = 0x10, rcur = 0x0 /* We must disable interrupts here */ disp %ctpr1, ipl_save ipd 3 call %ctpr1, wbs = 0x10 /* Store current IPL to dr9 */ addd 0, %db[0], %dr9 /* We have to flush both RF and CF to memory because saved values * of P[C]SHTP can be not valid here. */ E2K_ASM_FLUSH_CPU /* Load registers previously saved in setjmp. */ ldd [%dr0 + E2K_JMBBUFF_CR0_HI], %dr2 ldd [%dr0 + E2K_JMBBUFF_CR1_LO], %dr3 ldd [%dr0 + E2K_JMBBUFF_CR1_HI], %dr4 ldd [%dr0 + E2K_JMBBUFF_USD_LO], %dr5 ldd [%dr0 + E2K_JMBBUFF_USD_HI], %dr6 ldd [%dr0 + E2K_JMBBUFF_PSP_HI], %dr7 ldd [%dr0 + E2K_JMBBUFF_PCSP_HI], %dr8 rwd %dr2, %cr0.hi rwd %dr3, %cr1.lo rwd %dr4, %cr1.hi rwd %dr5, %usd.lo rwd %dr6, %usd.hi rwd %dr7, %psp.hi rwd %dr8, %pcsp.hi /* Enable interrupts */ addd 0, %dr9, %db[0] disp %ctpr1, ipl_restore ipd 3 call %ctpr1, wbs = 0x10 /* Actually, we return to setjmp caller with second * argument of longjmp stored on r1 register. */ adds 0, %r1, %r0 E2K_ASM_RETURN 

上下文切换


在弄清楚setjmp / longjmp之后,context_switch的基本实现对我们来说似乎很清楚。 实际上,与第一种情况一样,我们需要保存/恢复连接信息和堆栈的寄存器,此外,我们还需要正确恢复处理器状态寄存器(UPSR)。

我会解释。 与setjmp一样,保存寄存器时,必须首先将寄存器文件和绑定信息文件重置到内存(flushr + flushc)。 此后,我们需要保存寄存器CR0和CR1的当前值,以便在返回时跳转到当前流的确切切换位置。 接下来,我们保存PS,PCS和US堆栈的描述符。 最后,您需要注意中断模式的正确恢复-为此,我们还保存了UPSR寄存器。

汇编代码context_switch:

 C_ENTRY(context_switch): setwd wsz = 0x10, nfx = 0x0 /* Save prev UPSR */ rrd %upsr, %dr2 std %dr2, [%dr0 + E2K_CTX_UPSR] /* Disable interrupts before saving/restoring context */ rrd %upsr, %dr2 andnd %dr2, (UPSR_IE | UPSR_NMIE), %dr2 rwd %dr2, %upsr E2K_ASM_FLUSH_CPU /* Save prev CRs */ rrd %cr0.lo, %dr2 rrd %cr0.hi, %dr3 rrd %cr1.lo, %dr4 rrd %cr1.hi, %dr5 std %dr2, [%dr0 + E2K_CTX_CR0_LO] std %dr3, [%dr0 + E2K_CTX_CR0_HI] std %dr4, [%dr0 + E2K_CTX_CR1_LO] std %dr5, [%dr0 + E2K_CTX_CR1_HI] /* Save prev stacks */ rrd %usd.lo, %dr3 rrd %usd.hi, %dr4 rrd %psp.lo, %dr5 rrd %psp.hi, %dr6 rrd %pcsp.lo, %dr7 rrd %pcsp.hi, %dr8 std %dr3, [%dr0 + E2K_CTX_USD_LO] std %dr4, [%dr0 + E2K_CTX_USD_HI] std %dr5, [%dr0 + E2K_CTX_PSP_LO] std %dr6, [%dr0 + E2K_CTX_PSP_HI] std %dr7, [%dr0 + E2K_CTX_PCSP_LO] std %dr8, [%dr0 + E2K_CTX_PCSP_HI] /* Load next CRs */ ldd [%dr1 + E2K_CTX_CR0_LO], %dr2 ldd [%dr1 + E2K_CTX_CR0_HI], %dr3 ldd [%dr1 + E2K_CTX_CR1_LO], %dr4 ldd [%dr1 + E2K_CTX_CR1_HI], %dr5 rwd %dr2, %cr0.lo rwd %dr3, %cr0.hi rwd %dr4, %cr1.lo rwd %dr5, %cr1.hi /* Load next stacks */ ldd [%dr1 + E2K_CTX_USD_LO], %dr3 ldd [%dr1 + E2K_CTX_USD_HI], %dr4 ldd [%dr1 + E2K_CTX_PSP_LO], %dr5 ldd [%dr1 + E2K_CTX_PSP_HI], %dr6 ldd [%dr1 + E2K_CTX_PCSP_LO], %dr7 ldd [%dr1 + E2K_CTX_PCSP_HI], %dr8 rwd %dr3, %usd.lo rwd %dr4, %usd.hi rwd %dr5, %psp.lo rwd %dr6, %psp.hi rwd %dr7, %pcsp.lo rwd %dr8, %pcsp.hi /* Restore next UPSR */ ldd [%dr1 + E2K_CTX_UPSR], %dr2 rwd %dr2, %upsr E2K_ASM_RETURN 

另一个重要点是OS线程的初始化。 在Embox中,每个线程都有一个特定的主要过程

 void _NORETURN thread_trampoline(void); 

在其中流的所有其他工作将被执行。 因此,我们需要以某种方式准备用于调用此函数的堆栈,这是我们面临的事实,即存在三个堆栈,并且它们不在同一方向上增长。 根据体系结构,我们的流是用单个堆栈创建的,或者说是在堆栈下的单个位置,在顶部,我们有一个描述流本身的结构,依此类推,在这里,我们必须照顾不同的堆栈,不要忘记将它们对齐4 kB,请不要忘记各种访问权限,等等。

结果,目前我们决定将堆栈下的空间分为三部分,绑定信息堆栈下的四分之一,程序堆栈下的四分之一,用户堆栈下的四分之一。

我带了代码,以便您可以评估它的大小,您需要考虑这是最小的初始化。
 /* This value is used for both stack base and size align. */ #define E2K_STACK_ALIGN (1UL << 12) #define round_down(x, bound) ((x) & ~((bound) - 1)) /* Reserve 1/4 for PSP stack, 1/4 for PCSP stack, and 1/2 for USD stack */ #define PSP_CALC_STACK_BASE(sp, size) binalign_bound(sp - size, E2K_STACK_ALIGN) #define PSP_CALC_STACK_SIZE(sp, size) binalign_bound((size) / 4, E2K_STACK_ALIGN) #define PCSP_CALC_STACK_BASE(sp, size) \ (PSP_CALC_STACK_BASE(sp, size) + PSP_CALC_STACK_SIZE(sp, size)) #define PCSP_CALC_STACK_SIZE(sp, size) binalign_bound((size) / 4, E2K_STACK_ALIGN) #define USD_CALC_STACK_BASE(sp, size) round_down(sp, E2K_STACK_ALIGN) #define USD_CALC_STACK_SIZE(sp, size) \ round_down(USD_CALC_STACK_BASE(sp, size) - PCSP_CALC_STACK_BASE(sp, size),\ E2K_STACK_ALIGN) static void e2k_calculate_stacks(struct context *ctx, uint64_t sp, uint64_t size) { uint64_t psp_size, pcsp_size, usd_size; log_debug("Stacks:\n"); ctx->psp_lo |= PSP_CALC_STACK_BASE(sp, size) << PSP_BASE; ctx->psp_lo |= E2_RWAR_RW_ENABLE << PSP_RW; psp_size = PSP_CALC_STACK_SIZE(sp, size); assert(psp_size); ctx->psp_hi |= psp_size << PSP_SIZE; log_debug(" PSP.base=0x%lx, PSP.size=0x%lx\n", PSP_CALC_STACK_BASE(sp, size), psp_size); ctx->pcsp_lo |= PCSP_CALC_STACK_BASE(sp, size) << PCSP_BASE; ctx->pcsp_lo |= E2_RWAR_RW_ENABLE << PCSP_RW; pcsp_size = PCSP_CALC_STACK_SIZE(sp, size); assert(pcsp_size); ctx->pcsp_hi |= pcsp_size << PCSP_SIZE; log_debug(" PCSP.base=0x%lx, PCSP.size=0x%lx\n", PCSP_CALC_STACK_BASE(sp, size), pcsp_size); ctx->usd_lo |= USD_CALC_STACK_BASE(sp, size) << USD_BASE; usd_size = USD_CALC_STACK_SIZE(sp, size); assert(usd_size); ctx->usd_hi |= usd_size << USD_SIZE; log_debug(" USD.base=0x%lx, USD.size=0x%lx\n", USD_CALC_STACK_BASE(sp, size), usd_size); } static void e2k_calculate_crs(struct context *ctx, uint64_t routine_addr) { uint64_t usd_size = (ctx->usd_hi >> USD_SIZE) & USD_SIZE_MASK; /* Reserve space in hardware stacks for @routine_addr */ /* Remark: We do not update psp.hi to reserve space for arguments, * since routine do not accepts any arguments. */ ctx->pcsp_hi |= SZ_OF_CR0_CR1 << PCSP_IND; ctx->cr0_hi |= (routine_addr >> CR0_IP) << CR0_IP; ctx->cr1_lo |= PSR_ALL_IRQ_ENABLED << CR1_PSR; /* Divide on 16 because it field contains size in terms * of 128 bit values. */ ctx->cr1_hi |= (usd_size >> 4) << CR1_USSZ; } void context_init(struct context *ctx, unsigned int flags, void (*routine_fn)(void), void *sp, unsigned int stack_size) { memset(ctx, 0, sizeof(*ctx)); e2k_calculate_stacks(ctx, sp, stack_size); e2k_calculate_crs(ctx, (uint64_t) routine_fn); if (!(flags & CONTEXT_IRQDISABLE)) { ctx->upsr |= (UPSR_IE | UPSR_NMIE); } } 


本文还包含了有关中断,异常和计时器的内容,但是由于结果如此之大,我们决定在下一部分中进行讨论

以防万一,我重复一遍,该材料不是官方文档! 有关官方支持,文档和其他方面的信息,您需要直接与ICST联系。 Embox中的代码当然是开放的,但是要进行编译,您将需要一个交叉编译器,同样,也可以从MCST那里获得。

Source: https://habr.com/ru/post/zh-CN447704/


All Articles