CXL.cache Channel
将设备和主机之间的交互定义为多个请求,每个请求至少有一条相关的响应消息,有时还有数据传输。该接口在每个方向上由三个通道组成:请求(Request)、响应(Response)和数据(Data)。
通道按其方向和传输内容命名,D2H表示设备到主机,H2D表示主机到设备。
独立通道允许不同类型的信息使用专用线路,从而实现解耦和更高的单线有效吞吐量。
D2H Req通道,将新请求从设备传送到主机。请求通常以内存为目标。每个请求将收到0、1或2个响应,和最多一个64字节的缓存行。通道可以反压(Back Pressure)。
D2H Rsp通道,将设备的所有响应传送到主机。设备对监听的响应返回该缓存行在设备缓存中的状态,并可能表示数据正返回到主机提供的数据缓冲区。对于链路层信用,它们可能仍会被暂时阻止,但不应要求完成任何其他交易来释放信用。
D2H Data通道,将所有数据和字节启用从设备传输到主机。数据传输可以是隐式(由于监听)或显式写回(由于缓存容量而导致的“逐出”)的结果。在所有情况下,都将传输完整的64字节缓存行。D2H数据传输必须进行,否则可能会出现死锁。它们可能会因链路层信用暂时被阻止,但不得要求完成任何其他交易来释放信用。
H2D Req通道,将请求从主机传送到设备。这些请求都是为保持一致性的监听。可能会返回数据,请求携带数据缓冲区的位置,任何返回数据都应写入该缓冲区。
H2D Rsp通道,携带排序消息并提取写入数据。每个响应都携带来自原始设备请求的请求标识符,以指示响应应该路由到哪里。
H2D Data通道,为设备读取请求提供数据。在所有情况下,都将传输完整的64字节缓存行。
Channel Ordering
所有CXL.cache通道都必须彼此独立工作,例如,由于从设备向主机发出的对给定地址 X 的请求将被主机阻止,直到主机收集到对该地址 X 的所有窥探响应,因此将这些通道连接起来会导致死锁。
有一个特例,为了保证正确性,必须维护通道之间的顺序。主机需要等待设备观察到H2D响应上发送的全局排序(Global Ordering,GO)消息,然后再发送相同地址的后续监听。为了限制跟踪GO消息所需的缓冲量,主机保证通过CXL.cache发送的GO消息后,在一定周期内不能再发送的监听。
对于在单个通道上有多个具有预期顺序的报文的事务(如 WrInv 的 WritePull 和GO),设备/主机必须确保使用序列化报文来正确观察它们
Channel Crediting
每个通道都必须使用信用来发送任何消息,并从接收方收集信用返回。在操作过程中,每当接收器处理完消息后,它都会返回一个信用。如果没有可用的信用,发送方必须等待接收方返回信用。
D2H Req可以被无限期的阻塞,其他通道都可以有反压操作
表格描述哪些通道必须被排空才能继续,哪些通道可以无限期阻塞
CXL.cache Wire
每个消息将支持3种变体:68B Flit、256B Flit和PBR Flit;将在物理层中协商每种方法的使用
D2H Req
D2H Rsp
D2H Data
在 68B Flit 模式中,数据字节使能的存在会在 Flit 标头中显示,但只有当一个或多个字节使能位的值为 0 时才会显示。在这种情况下,字节使能会作为数据块发送
在 256B Flit 模式中,报文头包含一个 BEP(Byte-Enables Present)位,表示报文末尾包含 BE 插槽。字节使能字段宽 64 位,表示哪些字节对所含数据有效
H2D Req
H2D请求操作码与对应的D2H响应消息类型,X应该表示该请求不能有那样的响应消息类型
H2D Rsp
H2D Data
CXL.cache Transaction
当 CXL Type 2 设备使用主机管理设备内存一致性(HDM-D/HDM-DB)将内存暴露给主机时,设备负责解决主机和设备之间 HDM 的一致性问题。CXL 为此定义了两个协议选项:
用于 HDM-D的 CXL.cache 请求;与 HDM-DB 一起使用的 CXL.mem Back-Invalidate Snoop(BISnp)返回无效的窥探
D2H Req(与H2D Rsp配对)
对于设备到主机的请求,有四种不同的语义:CXL.cache读、CXL.cache读0、CXL.cache读0/写和CXL.cache写。
CXL.cache 读
device必须在CXL.cache d2h request通道上有来自host授权的信用,才能发起请求,跟CXL.io类似,每发送一个请求都会消耗掉一个信用。
发送该请求之后,device会在H2D response通道上收到来自于host的回应,该回应可以是一个GO(Global Observation)消息并带有64byte的cache line数据,也可以没有回应的GO消息。响应(GO)报文可以在数据报文之前、之后或之间收到。
响应(如果存在)和数据消息都指向初始D2H请求分组的CQID字段中提供的设备跟踪器条目。在收到来自主机的所有消息之前,设备条目必须保持活动状态。
为确保向前进度,设备必须具有保留的数据缓冲区,以便在发送请求后能够立即接受所有64字节的数据。但是,由于先前的数据返回没有排出,设备可能暂时无法接受来自主机的数据。一旦从主机接收到响应消息和数据消息,就可以认为事务已完成,并且条目已从设备中解除分配。
device里面有两个东西,一个是数据buffer,一个是tracker entry。
数据buffer是用来缓冲来自于host的数据,当device发出一个请求的时候,tracker entry会记录下请求的CQID,同时会在buffer中,给请求所对应的响应中的数据留出空间。收到了来自host的响应后,tracker entry 会去查看响应的CQID,同时数据也会放到buffer。一个请求,收到了与请求有相同CQID的响应,那我们认为这样一个事务才算结束。结束之后,buffer才可能在被使用,如果没有收到对应的请求,buffer就会一直预留那部分空间。如果空间都被预留了,应该就不会再发起请求,这样数据交互就会中断。
CXL.cache 读 0
CXL.cache Read0 必须有 D2H 请求信用,并在 D2H CXL.cache 请求通道上发送信息。
CXL.cache 读 0 请求会收到一条响应信息,但是没有数据消息。
响应消息指向初始D2H请求消息的CQID值中指示的设备条目。一旦收到这些请求的GO消息,就可以认为它们已完成,并且条目已从设备中解除分配。主机不得为这些事务发送数据消息。
CXL.cache 写
与读请求类似,发送请求之前,必须确保device中有信用。host接收到写请求之后,会回复GO和WritePull响应消息。响应消息可能会每个各回复一条,也有可能GP-I和WritePull消息合并之后一起发出。
在发出GO消息之前,一定不能先发出WritePull消息,它们两个消息一起发出可以。
如果事务是posted类型的写,host需要先发WritePull消息。如果是non-posted类型的写,host需要先发WritePull消息,在全局观察到non-posted写入时再发GO-I消息
协议中经常提到GO-I消息,里面也有GO消息,同时还存在GO-S、GO-E、GO-M、GO-Err消息,它们之间的区别可能是都是GO消息,只是它们响应数据RspData字段的4bit编码的MESI不同,导致了它们是不同的GO消息,在笔记4中有提到MESI与RspData字段。
在接收到GO-I消息后,设备将从排序和缓存一致性的角度考虑怎么写数据,放弃对缓存行的监听所有权。
WritePull响应消息会触发device发送64byte的数据到host,也可设置任意字节数的启用。
在device接到来自host的GO-I消息,并且将数据发送给了host,那么认为这样一个写事务就算完成了。同时,之前留出来的entry就会被清除掉。
如果host收到全部 64 字节数据,并且host发出了GO-I消息,那么认为这样一个写操作就完成了。
CXL.cache读0写
CXL.cache Read0-Write需要D2H请求信用。
WritePull消息会触发设备发出64-byte数据给主机,可以设置任意数量的字节使能
一旦设备接收到GO-I消息并发送了所有所需的数据消息,则认为CXL.cache Read0-Wrtie事务已完成。此时,可以从设备中释放条目。
主机在接收到所有64字节的数据并发送GO-I响应消息后,会认为Read0-Write结束
D2H Rsp(与H2D Rsp配对)
RdCurr:这些是来自设备的完整缓存行读取请求,以获取最新数据,但不会更改任何缓存(包括主机)中的现有状态。主机不需要跟踪发出RdCurr的设备中的缓存行。RdCurr获取数据,但无法占有,不需要host响应的GO消息。设备接收到处于无效状态的行,这意味着它只能使用一次该行,意味着有人使用了该缓存行,无法缓存该行。
RdOwn:来自设备的完整缓存行读取请求,用于缓存处于任何可写(writable)状态的行。通常,RdOwn请求接收处于Exclusive(GO-E)或Modified(GO-M)状态的行。device会接收到返回的GO消息。处于修改状态的缓存行数据不能丢弃,必须写回到host中。在错误情况下,RdOwn请求可能会收到处于Invalid(GO-I)或Error(GO-Err)状态的行。两者都将返回全是1的数据。设备负责处理错误。
RdShared:设备发起读取完整缓存行的请求,用于缓存处于共享状态的行。通常,RdShared请求接收处于共享(GO-S)状态的行。在错误情况下,RdShared请求可能会收到处于Invalid(GO-I)或Error(GO-Err)状态的行。两者都将返回全是1的数据。设备负责处理错误。
RdAny:设备发起读、读取完整缓存行的请求,用于缓存处于任何状态的行。device会接收到返回的GO消息。处于修改状态的缓存行数据不能丢弃,必须写回到host中。在错误情况下,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的独占缓存行。该请求会收到GO-WritePull或GO-WritePullDrop类型的响应消息。不论设备收到哪种响应消息,都必须放弃对该缓存行的监听所有权限。如果收到的是GO-WritePull,那么设备将数据发送给主机,如果是GO-WritePullDrop,那么主机直接将该组数据丢弃。
DirtyEvict:设备发送请求给主机,要求evict清除一条64-byte的处于修改状态的缓存行。请求发出后,应该收到的响应消息是GO-WritePull,这也意味着device对该缓存行失去了监听所有权,并需要将数据发送出去。一旦设备发起了这样的一个请求,并且在设备接收到GO-WritePul或GO-WritePullDrop响应消息前,访问的缓存地址已经被监听了,这时候,设备需要设置数据消息的Bogus字段为1,用来指示这个数据也许不是最新的。当有错误出现,主机回复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字节的写无效行请求,WOWrInv不同之处在于,它不是弱排序的。通常,WrInv会收到一个WritePull,然后是GO。获得WritePull后,设备将数据发送到主机。一旦内存中的写入完成(主机挂载内存或设备挂载内存),主机将发送GO。在错误情况下,会收到GO-Err。设备负责处理错误。
CacheFlushed:设备通过此请求通知主机其缓存已刷新,并且不再包含处于Shared、Exclusive或Modified状态的任何缓存行。主机可以使用此信息清除其监听过滤器,阻止对设备的监听并返回GO。一旦设备接收到GO,在设备发送下一个可缓存D2H请求之前,保证不会从主机接收任何监听。
D2H请求与收到的对应的响应消息类型,主要针对的是访文目标非device附属的存储
CleanEvict, DirtyEvict and CleanEvictNoData请求访问目标是device附属存储的,不用管偏执类型,都是在device内部完成。
访问针对的device附属的存储,参考表
D2H Rsp(与H2D Req配对)
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:缓存行的状态没有发生变化。设备仅把当前的数据发送给主机。
H2D Req(与D2H Rsp配对)
主机可以按照与数据消息的任何相对顺序接收响应消息。对于监听数据传输,字节启用字段始终为1,也就是全部启用。
H2D监听完成的过程
SnpData:主机发起的监听请求,主要目的是读device中cacheline的数据。读哪些cacheline的数据?主要是读那些在host内部将要变成共享和独占状态的cacheline。主机想将这条缓存行状态修改为Shared或者是Exclusive。设备响应一般发送数据。设备接到该请求后,必须无效掉自己的副本或者降级修改状态为Shared。如果设备修改过此缓存行,必须把“脏”数据发送给主机。
一个有效的cache行,它的内容没有被更高层内存或CPU修改写过,我们称这个cache行是“干净”的,显然相反状态是dirty(“脏”)。
SnpInv:主机发起的监听请求,主机想拥有这条缓存行的所有权,状态修改为Exclusive。这种请求一般是由于主机要对该缓存行进行写操作。如果设备内有“脏”数据,那么必须将该数据返回给host。
SnpCur:该监听请求是为了获得最新的缓存行数据,但是并不改变它的现有状态。如果设备中的缓存数据处于Modified状态,必须返回一份数据给主机。主机和设备两端的缓存行状态不需要改变,主机不更新缓存。
H2D请求和相关的响应
H2D Rsp(与D2H Req配对)
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,设备一定要把数据发送给主机,主机会将该数据丢弃。主机不需要传输缓存状态给设备。
Restrictions
-
主机的GO-M响应表明设备被授予修改数据(modified data)的唯一副本。设备必须缓存此数据,并在完成后将其写回
-
当主机向设备返回GO响应时,能看到对相同地址的监听该GO请求的结果。如果主机发送GO-E请求RdOwn,然后紧接着向同一地址发送监听,接下来会期望设备将缓存行转换为M状态,并且返回RspIFwdM响应给主机。为了实现这一目的,CXL.cache链路层确保设备将接收这两条消息,以使顺序完全明确。当主机向设备发送监听请求时,要求在主机收到监听响应并收到所有隐式写回(implicit writeback,IWB)数据之前,不会向设备中含有该地址的任何请求发送GO响应。当主机向设备返回数据,并且该请求的GO尚未发送到设备时,主机可能在发送GO消息之前不会向该地址发送监听请求。
-
主机收到来自64字节地址的所有WritePull数据之前,主机不可以对该地址启动监听。相反,在主机收到对挂起的写入地址监听的响应之前,主机不可以启动WritePull。
-
如果已在CXL.cache D2H请求通道上发出设备逐出事务,但尚未从主机收到WritePull,并且监听命中WB,则设备必须跟踪此监听命中。当设备开始处理WritePull时,设备必须在发送到主机的所有D2H数据消息中设置Bogus字段。目的是向主机传达请求数据已作为IWB数据发送,因此逐出的数据可能已过时。
-
只允许主机在某一时刻对指定设备的指定缓存行地址有一个未完成监听。也就是说,主机必须等到接收到监听响应和所有IWB数据(如果有)后,才能将下一个监听请求发送到该地址。
-
只有在以下特定情况下,才允许对同一缓存行执行多个读请求。无论请求的处理顺序如何,主机的跟踪状态都是一致的,主机可自由重新排列请求,因此设备负责在需要时对请求进行排序。
-
不允许对同一缓存行发起多次逐出请求
-
允许在CXL.cache上对同一缓存行执行多个WrInv/WOWrInv/ItoMWr/MemWr。主机或交换机可以自由的进行重新排序请求,并且设备按照重新排序的顺序接收相应的H2D响应,发出序列化读取后,在收到 GO 之前,不得对同一高速缓存行地址进行其他访问
-
只有在主机保证请求将拥有缓存行的所有权之后,才会发送GO响应
-
FastGO只允许用于不需要严格排序的请求,要求是有一个最终完成(ExtCmp)信息,表明该请求处于整个系统都能完全观察到的阶段
-
CXL.cache 不允许向设备连接的内存进行设备撤消,设备可以使用非唤回写入(如 ItoMWr、WrCur)将数据写入主机的设备附加内存
-
要在CXL.cache上发出请求,设备需要通过CXL.io上的ATS请求从主机获取主机物理地址。