3.2 CXL.cache
3.2.1 概览
CXL.cache协议将设备和主机之间的交互定义为多个请求,每个请求至少有一条相关的响应消息,有时还有数据传输。该接口在每个方向上由三个通道组成:请求(Request)、响应(Response)和数据(Data)。通道按其方向命名,D2H表示设备到主机,H2D表示主机到设备。
-
D2H请求通道,将新请求从设备传送到主机。请求通常以内存为目标。每个请求将收到0、1或2个响应,和最多一个64字节的缓存行。通道可以反压(Back Pressure)。
-
D2H响应通道,将设备的所有响应传送到主机。设备对监听的响应返回该缓存行在设备缓存中的状态,并可能表示数据正返回到主机提供的数据缓冲区。对于链路层信用,它们可能仍会被暂时阻止,但不应要求完成任何其他交易来释放信用。
-
D2H数据通道,将所有数据和字节启用从设备传输到主机。数据传输可以是隐式(由于监听)或显式写回(由于缓存容量而导致的"逐出")的结果。在所有情况下,都将传输完整的64字节缓存行。D2H数据传输必须进行,否则可能会出现死锁。它们可能会因链路层信用暂时被阻止,但不得要求完成任何其他交易来释放信用。
-
H2D请求通道,将请求从主机传送到设备。这些请求都是为保持一致性的监听。可能会返回数据,请求携带数据缓冲区的位置,任何返回数据都应写入该缓冲区。
-
H2D响应通道,携带排序消息并提取写入数据。每个响应都携带来自原始设备请求的请求标识符,以指示响应应该路由到哪里。
-
H2D数据通道,为设备读取请求提供数据。在所有情况下,都将传输完整的64字节缓存行。
3.2.2 CXL.cache通道描述
通常,所有CXL.cache通道都必须彼此独立工作。然而,有一个特例,为了保证正确性,必须维护通道之间的顺序。主机需要等待设备观察到H2D响应上发送的全局排序(Global Ordering,GO)消息,然后再发送相同地址的后续监听。为了限制跟踪GO消息所需的缓冲量,主机保证通过CXL.cache发送的GO消息后,在一定周期内不能再发送的监听。
每个通道都必须使用信用来发送任何消息,并从接收方收集信用返回。在操作过程中,每当接收器处理完消息后,它都会返回一个信用。如果没有可用的信用,发送方必须等待接收方返回信用。下表描述了哪些通道必须被排空才能继续,哪些通道可以无限期阻塞。
3.2.3 CXL.cache写描述
CXL.cache每个通道消息的字段定义如下。
3.2.3.1 D2H请求
关于Opcode,后面的章节会详细描述这些D2H请求。
3.2.3.2 D2H响应
3.2.3.3 D2H数据
3.2.3.4 H2D请求
3.2.3.5 H2D响应
3.2.3.6 H2D数据
3.2.4 CXL.cache事务描述
3.2.4.1 D2H请求
D2H请求,有四种语义:CXL.cache Read,CXL.cache Read0,CXL.cache Read0/Write,CXL.cache Write。
CXL.cache Read
CXL.cache Read需要D2H请求信用,在D2H CXL.cache请求通道上发送请求消息。CXL.cache Read请求需要0或1条响应(GO)消息和64字节缓存行的数据信息。响应(如果存在)和数据消息都指向初始D2H请求分组的CQID字段中提供的设备跟踪器条目。在收到来自主机的所有消息之前,设备条目必须保持活动状态。为确保向前进度,设备必须具有保留的数据缓冲区,以便在发送请求后能够立即接受所有64字节的数据。但是,由于先前的数据返回没有排出,设备可能暂时无法接受来自主机的数据。一旦从主机接收到响应消息和数据消息,就可以认为事务已完成,并且条目已从设备中解除分配。
下图显示了CXL.cache Read的过程。需注意,响应(GO)消息可以在数据消息之前、之后或之间接收。
CXL.cache Read0
CXL.cache Read0接受响应消息,但是没有数据消息。响应消息指向初始D2H请求消息的CQID值中指示的设备条目。一旦收到这些请求的GO消息,就可以认为它们已完成,并且条目已从设备中解除分配。主机不得为这些事务发送数据消息。
CXL.cache Write
CXL.cache Write需要D2H请求信用。主机收到请求消息后,需要发送两条单独的或一条合并的GO-I和WritePull消息。GO消息绝不能在WritePull之前到达设备,但它可以在组合消息中到达。如果事务是posted类型的写,那么合并的回复消息类型更能派上用场。如果是non-posted类型的写,host需要先发GO-I消息,再发WritePull消息。
下图显示了完成CXL.cache Write事务的过程,WritePull消息触发数据消息。
下图显示了WritePull是来自GO的单独消息的情况(强序不可缓存写请求)。
下图显示了主机FastGO和ExtCmp的响应(弱序写请求)。
至于什么是强序,弱序,就不解释了。
CXL.cache Read0-Write
CXL.cache Read0-Write需要D2H请求信用。WritePull消息会触发设备发出64-byte数据给主机。一旦设备接收到GO-I消息并发送了所有所需的数据消息,则认为CXL.cache Read0-Wrtie事务已完成。此时,可以从设备中释放条目。主机在接收到所有64字节的数据并发送GO-I响应消息后,会认为Read0-Write结束。
D2H请求的opcode如下:
-
RdCurr:这些是来自设备的完整缓存行读取请求,以获取最新数据,但不会更改任何缓存(包括主机)中的现有状态。主机不需要跟踪发出RdCurr的设备中的缓存行。RdCurr获取数据,但无法占有。设备接收到处于无效状态的行,这意味着它只能使用一次该行,无法缓存该行。
-
RdOwn:来自设备的完整缓存行读取请求,用于缓存处于任何可写(writable)状态的行。通常,RdOwn请求接收处于Exclusive(GO-E)或Modified(GO-M)状态的行。处于Modified状态的缓存行,必须将其写回主机。在错误情况下,RdOwn请求可能会收到处于Invalid(GO-I)或Error(GO-Err)状态的行。两者都将返回全是1的数据。设备负责处理错误。
-
RdShared:设备发起读取完整缓存行的请求,用于缓存处于Shared状态的行。通常,RdShared请求接收处于共享(GO-S)状态的行。在错误情况下,RdShared请求可能会收到处于Invalid(GO-I)或Error(GO-Err)状态的行。两者都将返回全是1的数据。设备负责处理错误。
-
RdAny:备发起读、读取完整缓存行的请求,用于缓存处于任何状态的行。在错误情况下,RdAny请求可能会收到处于Invalid(GO-I)或Error(GO-Err)状态的行。两者都将返回全是1的数据。设备负责处理错误。
-
RdOwnNoData:设备向指定的缓存行地址发起该请求,目的是拥有独占的权限。在错误情况下,RdOwnNoData请求可能会收到处于Error(GO-Err)状态的行。设备负责处理错误。
-
ItoMWr:设备对指定的缓存行地址发起独占权限请求,并以原子方式将缓存行写回主机。由于设备会将整个缓存行的数据都修改掉,所以不需要主机将该缓存行数据发给设备。当主机给了请求该缓存行的独占权限后,设备会回复响应消息GO-I-WritePull。设备不需要再保留写回的数据。如果错误发生,设备回复GO-Err-WritePull,主机丢弃设备写回的数据。设备处理该错误。
-
MemWr:与ItoMWr有些类似,但是写回数据的位置可能不同。只有当命令命中缓存时,数据才会写入其中;如果未命中,数据将直接写入内存。一旦请求被授予所有权,典型的响应就是GO-I-WritePull。设备不保留该行的副本。如果错误发生,设备回复GO-Err-WritePull,主机丢弃设备写回的数据。设备处理该错误。
-
ClFlush:设备发起请求,无效掉指定地址的缓存行。主机返回的典型响应是GO-I。
-
CleanEvict:设备发送请求给主机,要求evict一条64-byte的Exclusive缓存行。该请求会收到GO-WritePull或GO-WritePullDrop类型的响应消息。不论设备收到哪种响应消息,都必须放弃对该缓存行的监听所有权限。如果收到的是GO-WritePull,那么设备将数据发送给主机,如果是GO-WritePullDrop,那么主机直接将该组数据丢弃。
-
DirtyEvict:设备发送请求给主机,要求evict一条64-byte的Modified缓存行。请求发出后,应该收到的响应消息是GO-WritePull,这也意味着device对该缓存行失去了监听所有权,并需要将数据发送出去。一旦设备发起了这样的一个请求,并且在设备接收到GO-WritePul或GO-WritePullDrop响应消息前,访问的缓存地址已经被监听了,这时候,设备需要设置数据消息的Bogus字段,用来指示这个数据也许不是最新的。当有错误出现,主机回复GO-Err-WritePull,这个时候设备写回的数据会被主机丢弃掉。设备负责处理错误。
-
CleanEvictNoData:设备发起请求给主机,以便在设备中删除一条干净的缓存行。此请求的唯一目的是更新主机中的监听过滤器(snoop filter),并且不会交换任何数据。只有在主机挂载内存的地址范围内,才需要CleanRecictNodeData。对于设备挂载内存的地址范围,等效操作可以在设备内部完成,而无需向主机发送事务。
-
WOWrInv:弱顺序写无效缓存行(0-63byte)请求,可以设置字节启用组合。通常,WOWrInv接收一个FastGO-WritePull,后跟一个ExtCmp。收到FastGO-WritePull后,设备会将数据发送到主机。对于主机挂载内存,一旦内存中的写入完成,主机就会发送ExtCmp。在错误情况下,将收到GO-Err-Writepull。设备将正常发送数据,但主机将丢弃数据。设备负责处理错误。在所有情况下,在GO错误之后,主机仍将发送ExtCmp。
-
WOWrInvF:与WOWrInv类似,不同的是它是一条完整的缓存行(64byte),没有字节使能。
-
WrInv:这是一个0-64字节的写无效行请求。通常,WrInv会收到一个WritePull,然后是GO。获得WritePull后,设备将数据发送到主机。一旦内存中的写入完成(主机挂载内存或设备挂载内存),主机将发送GO。在错误情况下,会收到GO-Err。设备负责处理错误。
-
CacheFlushed:设备通过此请求通知主机其缓存已刷新,并且不再包含处于Shared、Exclusive或Modified状态的任何缓存行。主机可以使用此信息清除其监听过滤器,阻止对设备的监听并返回GO。一旦设备接收到GO,在设备发送下一个可缓存D2H请求之前,保证不会从主机接收任何监听。
下表是D2H请求与对应的H2D响应(非设备挂载内存)
下表是D2H请求与对应的响应(设备挂载内存)
3.2.4.2 D2H响应
D2H响应编码:
-
RspIHitI:这是设备对主机的监听请求的响应,表示在设备端没有此缓存行副本。如果设备返回RspIHitI,主机可以认为该缓存行已从设备中清除。
-
RspVHitV:这是设备对主机的监听请求的响应,表示在设备端有此缓存行副本,但是状态没有改变。如果设备返回RspVHitV,主机可以认为设备至少有一个该缓存行的副本。
-
RspIHitSE:这是设备对主机的监听请求的响应,表示在设备端该缓存行是干净的,而且现在无效了。如果设备返回RspIHitSE,主机可以认为该缓存行已经从设备清掉了。
-
RspSHitSE:这是设备对主机的监听请求的响应,表示在设备端该缓存行是干净的,而且现在降级为Shared。如果设备返回RspSHitSE,主机将认为此缓存行副本还保留在设备中。
-
RspSFwdM:H2D的监听请求命中的缓存行是Modified状态,之后变为了Shared状态。设备可以将状态变为Invalid。设备会通过D2H CXL.cache数据通道发送64-byte给主机。
-
RspIFwdM:H2D的监听请求命中的缓存行是Modified状态,之后变为了Invalid状态。主机可以认为设备不再有该缓存行的副本。设备会通过D2H CXL.cache数据通道发送64-byte给主机。
-
RspVFwdV:缓存行的状态没有发生变化。设备仅把当前的数据发送给主机。
3.2.4.3 H2D请求
下图是H2D监听完成的过程。主机可以按照与数据消息的任何相对顺序接收响应消息。对于监听数据传输,字节启用字段始终为1,也就是全部启用。
H2D请求和相关的响应:
-
SnpData:主机发起的监听请求,主机想将这条缓存行状态修改为Shared或者是Exclusive。设备响应一般发送数据。设备接到该请求后,必须无效掉自己的副本或者降级修改状态为Shared。如果设备修改过此缓存行,必须把"脏"数据发送给主机。
-
SnpInv:主机发起的监听请求,主机想拥有这条缓存行的所有权,状态修改为Exclusive。这种请求一般是由于主机要对该缓存行进行写操作。如果设备内有"脏"数据,那么必须将该数据返回给host。
-
SnpCur:该监听请求是为了获得最新的缓存行数据,但是并不改变它的现有状态。如果设备中的缓存数据处于Modified状态,必须返回一份数据给主机。主机和设备两端的缓存行状态不需要改变,主机不更新缓存。
3.2.4.4 H2D响应
H2D响应编码:
-
WritePull:主机告诉设备,把数据发给主机,但是不用改变缓存行的状态。用于WrInv请求,该消息需要先于GO-I消息发出,因为GO-I消息发出,意味着I/O的写操作已经完成。
-
GO:(Global Observation)表示读请求是一致的,写请求是一致的,连贯的。系统设备已观察到该事务,RspType字段中编码的MESI状态表示与该事务相关的数据应置于请求者缓存的哪个状态。
-
GO_WritePull:这是GO+WritePull消息。没有缓存状态需要传输给设备。GO+WritePull消息用于posted写类型
-
ExtCmp:系统观察到了有Fast_GO数据。访问存储会得到最新的数据
-
GO_WritePull_Drop:此消息的语义与Go_WritePull相同,只是设备不应向主机发送数据。当主机确定不需要数据时,可以发送此响应来代替GO_WritePull。由于始终需要传输字节启用,因此不会为部分写入发送此响应
-
Fast_GO:与GO消息类似,区别是,该消息意味着只有本地知道了,而GO消息是全系统知道了。当事务是完全可观测的时候,在该消息后面会发出ExtCmp消息。如果设备不支持该消息,则忽略这条消息,一直等待ExtCmp消息。
-
Fast_GO_WritePull:与GO_WritePull类似,但是该消息告诉设备,请求只是被本地观测到。当事务是完全可观测的时候,在该消息后面会发出ExtCmp消息。如果设备不支持该消息,则忽略这条消息,一直等待ExtCmp消息。主机不需要传输缓存状态给设备。
-
GO_ERR_WritePull:与GO_WritePull类似,但该消息是告诉设备,请求出现了错误,需要设备去处理。对于WritePull,设备一定要把数据发送给主机,主机会将该数据丢弃。主机不需要传输缓存状态给设备。
3.2.5 可缓存性详述和请求限制
本小节的细节和限制适用于所有的设备。
3.2.5.1 GO-M响应
主机的GO-M响应表明设备被授予修改数据(modified data)的唯一副本。设备必须缓存此数据,并在完成后将其写回。
3.2.5.2 Device/Host Snoop-GO-Data假设
当主机向设备返回GO响应时,期望到达接收GO的请求的相同地址的监听将看到该GO的结果。例如,如果主机发送GO-E请求RdOwn,然后紧接着向同一地址发送监听,接下来会期望设备将缓存行转换为M状态,并且返回RspIFwdM响应给主机。为了实现这一目的,CXL.cache链路层确保设备将接收这两条消息,以使顺序完全明确。当主机向设备发送监听请求时,要求在主机收到监听响应并收到所有隐式写回(implicit writeback,IWB)数据之前,不会向设备中含有该地址的任何请求发送GO响应。当主机向设备返回数据,并且该请求的GO尚未发送到设备时,主机可能在发送GO消息之前不会向该地址发送监听请求。
从根本上讲,与读取请求相关的GO也适用于该请求返回的数据。为读取请求发送数据意味着数据有效,这意味着即使GO尚未到达,设备也可以使用它。
3.2.5.3 Device/Host Snoop/WritePull假设
在主机收到来自64字节地址的所有WritePull数据之前,主机不可以对该地址启动监听。相反,在主机收到对挂起的写入地址监听的响应之前,主机不可以启动WritePull。
1.2.5.4 CXL.cache上逐出(Evicts)的监听响应和数据传输
如果已在CXL.cache D2H请求通道上发出设备逐出事务,但尚未从主机收到WritePull,并且监听命中WB,则设备必须跟踪此监听命中。当设备开始处理WritePull时,设备必须在发送到主机的所有D2H数据消息中设置Bogus字段。目的是向主机传达请求数据已作为IWB数据发送,因此逐出的数据可能已过时。
3.2.5.5 对相同地址的多个监听
只允许主机在某一时刻对指定设备的指定缓存行地址有一个未完成监听。也就是说,主机必须等到接收到监听响应和所有IWB数据(如果有)后,才能将下一个监听请求发送到该地址。
3.2.5.6 对同一缓存行的多个读请求
只有在以下特定情况下,才允许对同一缓存行执行多个读请求。
3.2.5.7 对同一缓存行的多个逐出
不允许对同一缓存行发起多次逐出请求。
3.2.5.8 对同一缓存行的多个写请求
允许在CXL.cache上对同一缓存行执行多个WrInv/WOWrInv/ItoMWr/MemWr。主机或交换机可以自由的进行重新排序请求,并且设备按照重新排序的顺序接收相应的H2D响应。但是,通常不建议设备对同一缓存行发出多个写请求。
3.2.5.9 GO
只有在主机保证请求将拥有缓存行的所有权之后,才会发送GO响应。
3.2.5.10 FastGO
FastGO只允许用于不需要严格排序的请求。
3.2.5.11 逐出到设备挂载的内存
设备仅允许向设备挂载的内存发出WrInv和WOWrInv*。
3.2.5.12 CXL.cache上的内存类型
要在CXL.cache上发出请求,设备需要通过CXL.io上的ATS请求从主机获取主机物理地址。
3.2.5.13 一般假设
略。
3.2.5.14 Buried Cache State Rules
略。