CRS全称是configuration retry status,单纯从名称上看应该是和配置访问有一定的关系,感觉又是在retry。那CRS到底是用来做什么的?其实很简单,当pcie设备还没有准备好响应配置请求时(比如自己的FW还在初始化中),如果其他设备(host)对该设备发起配置访问,将会收到状态CRS的completion包。如果此时抓trace的话,将会发现completion 包的status就是010的。
既然设备还没有准备好,返回了CRS的completion包,那么芯片和软件应该怎么处理这种情况呢?协议有下面的说明
(1)当CRS software visibility 没有使能的情况下,(即这种情况是需要硬件(RC)重发配置请求):
RC需要重发一个新的配置请求(一般芯片有一些芯片特有的reg,可以配置硬件进行重发的时间间隔和重发次数)。
(2)当CRS software visibility使能的情况,(即这种情况是需要软件(linux host driver)重发配置请求):
a.如果是对配置空间的VendorID字段进行的配置读请求,对于Vendorid字段,RC需要返回0001h的的返回值,对于其他字段需要返回全1。
b.对于配置写请求或者配置读请求,RC必须重新发送新的请求(这个就要求软件要重发了)。
既然协议规定了可以把CRS的处理给软件,那么哪些reg可以通知芯片,软件来接管处理CRS,不需要硬件重发配置请求?答案就是配置空间的CRS software visibility enable bit。
配置空间的root control reg的CRS software visibility enable bit为1时,会把CRS的completion包的处理交给软件接管。
linux kernel会把根据是否支持CRS Software Visibility这种cap来使能CRS Software Visibility bit。
那么如果使能了CRS Software Visibility,软件会进行怎么样的操作呢,下面是linux kernel对于CRS的处理:
bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *l,
int crs_timeout)
{
int delay = 1;
if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, l))
return false;
--------(1)读取配置空间的vendorid和deviceid
/* some broken boards return 0 or ~0 if a slot is empty: */
if (*l == 0xffffffff || *l == 0x00000000 ||
*l == 0x0000ffff || *l == 0xffff0000)
return false;
--------(2)判断寄存器是否是全1或者全0,低16bit是vendorid,高16bit是deviceid,如果是全1或者0,可能是slot下面没有接东西
/*
* Configuration Request Retry Status. Some root ports return the
* actual device ID instead of the synthetic ID (0xFFFF) required
* by the PCIe spec. Ignore the device ID and only check for
* (vendor id == 1).
*/
while ((*l & 0xffff) == 0x0001) { -------------(3)判断vendorid是否0x0001,如果为0x0001则对端设备返回了CRS的completion
if (!crs_timeout)
return false;
msleep(delay);-------(4)延时1ms
delay *= 2;-------(5)每次返回CRS的completion包后,延时时间会变成2倍。
if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, l))--------(5)重发配置读请求访问vendorid
return false;
/* Card hasn't responded in 60 seconds? Must be stuck. */
if (delay > crs_timeout) {
printk(KERN_WARNING "pci %04x:%02x:%02x.%d: not responding\n",
pci_domain_nr(bus), bus->number, PCI_SLOT(devfn),
PCI_FUNC(devfn));
return false;
}
}
return true;
}