文章目录
前言
CXL 是一个比较新的技术,内核版本迭代太快,跟不上节奏,固定一个版本是不行了。
在阅读之前,希望读者能有一定的 PCIe/CXL 基础知识,精力有限,不能把所有知识点都能说的很详细,需要一定的基础才能理解,同时,希望在学习的过程中,手边能有 PCIe Spec 以及 CXL 2.0 /3.1 Spec,以便随时查看,当然,我也会尽量把重点的部分截图在博文中。
最后,如果有问题请留言讨论。
Ref
《PCI_Express_Base_5.0r1.0》
《CXL Specification_rev2p0_ver1p0_2020Oct26》
《CXL-3.1-Specification》
正文
1. cxl_setup_regs
int cxl_setup_regs(struct cxl_register_map *map)
{
int rc;
// 对 map 所代表的寄存器块进行物理地址到虚拟地址的映射,大小到 bar 空间尾部
rc = cxl_map_regblock(map);
if (rc)
return rc;
// 获取寄存器块的信息, 位置,大小等
rc = cxl_probe_regs(map);
// 解除映射
cxl_unmap_regblock(map);
return rc;
}
2. cxl_probe_regs()
static int cxl_probe_regs(struct cxl_register_map *map)
{
struct cxl_component_reg_map *comp_map;
struct cxl_device_reg_map *dev_map;
struct device *host = map->host;
void __iomem *base = map->base;
// 根据寄存器块的类型进行处理
switch (map->reg_type) {
case CXL_REGLOC_RBI_COMPONENT:
// 处理 component Register
comp_map = &map->component_map;
// 下文分析,主要获取寄存器id, 位置和大小信息保存在 comp_map 中
cxl_probe_component_regs(host, base, comp_map);
dev_dbg(host, "Set up component registers\n");
break;
case CXL_REGLOC_RBI_MEMDEV:
// 处理 CXL Memory Device Register
// 找到的寄存器组地址和大小信息保存在 dev_map 中
dev_map = &map->device_map;
cxl_probe_device_regs(host, base, dev_map);
if (!dev_map->status.valid || !dev_map->mbox.valid ||
!dev_map->memdev.valid) {
dev_err(host, "registers not found: %s%s%s\n",
!dev_map->status.valid ? "status " : "",
!dev_map->mbox.valid ? "mbox " : "",
!dev_map->memdev.valid ? "memdev " : "");
return -ENXIO;
}
dev_dbg(host, "Probing device registers...\n");
break;
default:
break;
}
return 0;
}
3. cxl_probe_component_regs()
// 探测 CXL 组件寄存器块, 找到设备中的 HDM Decoder 寄存器并记录位置和大小
// dev : CXL 设备文件
// base : 包含 HDM 解码能力头的映射基地址,就是寄存器块所在位置的基地址,已映射后的虚拟地址
// map : 描述发现的寄存器块信息的对象
// Ref CXL 2.0r 8.1.9 Register Locator DVSEC
// 8.2.5 CXL.cache and CXL.mem Registers
void cxl_probe_component_regs(struct device *dev, void __iomem *base,
struct cxl_component_reg_map *map)
{
int cap, cap_count;
u32 cap_array;
*map = (struct cxl_component_reg_map) { 0 };
/*
* CXL.cache and CXL.mem registers are at offset 0x1000 as defined in
* CXL 2.0 8.2.4 Table 141. 如上图
*/
// CXL_CM_OFFSET 0x1000
// CXL_CM_OFFSET 偏移处为 .cache 和 .mem 的寄存器位置
base += CXL_CM_OFFSET;
cap_array = readl(base + CXL_CM_CAP_HDR_OFFSET);
// 见下图 CXL.cache and CXL.mem Architectural Register Header
// 或Ref CXL 2.0 8.2.5.1 CXL Capability Header Register
// CXL Capability Header Register 的 CXL_Capability_ID (0:15) 必须为 1,不为 1 报错
// #define CXL_CM_CAP_HDR_ID_MASK GENMASK(15, 0)
// #define CM_CAP_HDR_CAP_ID 1
if (FIELD_GET(CXL_CM_CAP_HDR_ID_MASK, cap_array) != CM_CAP_HDR_CAP_ID) {
dev_err(dev,
"Couldn't locate the CXL.cache and CXL.mem capability array header.\n");
return;
}
/* It's assumed that future versions will be backward compatible */
// 见下图 CXL.cache and CXL.mem Architectural Register Header
// 或Ref CXL 2.0 8.2.5.1 CXL Capability Header Register
// 31:24 Array_Size 定义了存在的元素数目
// #define CXL_CM_CAP_HDR_ARRAY_SIZE_MASK GENMASK(31, 24)
cap_count = FIELD_GET(CXL_CM_CAP_HDR_ARRAY_SIZE_MASK, cap_array);
// 遍历,每个元素 4 个字节,首个DWORD 为头,略过
for (cap = 1; cap <= cap_count; cap++) {
void __iomem *register_block;
u32 hdr;
int decoder_cnt;
u16 cap_id, offset;
u32 length;
hdr = readl(base + cap * 0x4);
// 读 Capability Header id 与偏移
cap_id = FIELD_GET(CXL_CM_CAP_HDR_ID_MASK, hdr);
offset = FIELD_GET(CXL_CM_CAP_PTR_MASK, hdr);
register_block = base + offset;
// Ref CXL 2.0 8.2.5.5 CXL HDM Decoder Capability Header
// 或上图 CXL HDM Decoder Capability Header
// CXL_CM_CAP_CAP_ID_HDM == 0x5
// 只处理 HDM Decoder Registers
switch (cap_id) {
case CXL_CM_CAP_CAP_ID_HDM:
// #define CXL_CM_CAP_CAP_ID_HDM 0x5
// 8.2.5.5 CXL HDM Decoder Capability Header, 如下图
// CXL_Capability_ID 为 5
dev_dbg(dev, "found HDM decoder capability (0x%x)\n",
offset);
// 先读 4 字节
// 8.2.5.12.1 CXL HDM Decoder Capability Register (Offset 00h)
hdr = readl(register_block);
// Ref 下图 CXL HDM Decoder Capability Structure
// or CXL 2.0 8.2.5.12 CXL HDM Decoder Capability Structure
// 根据 bit 3:0 获取 number of memory address decoders
decoder_cnt = cxl_hdm_decoder_count(hdr);
// 8.2.5.12 CXL HDM Decoder Capability Structure
// 长度计算公式,设备的是 0x20 * decoder_cnt + 0x10
length = 0x20 * decoder_cnt + 0x10;
rmap = &map->hdm_decoder;
break;
// ID 为 2
case CXL_CM_CAP_CAP_ID_RAS:
// #define CXL_CM_CAP_CAP_ID_RAS 0x2
// 8.2.5.2 CXL RAS Capability Header
dev_dbg(dev, "found RAS capability (0x%x)\n",
offset);
// 长度包括0x18 的寄存器,最后一个寄存器是日志寄存器,包括 0x40 个字节的日志,共 0x58(CXL_RAS_CAPABILITY_LENGTH)
length = CXL_RAS_CAPABILITY_LENGTH;
rmap = &map->ras;
break;
default:
dev_dbg(dev, "Unknown CM cap ID: %d (0x%x)\n", cap_id,
offset);
break;
}
if (!rmap)
continue;
// 保存内容
rmap->valid = true;
rmap->id = cap_id;
rmap->offset = CXL_CM_OFFSET + offset;
rmap->size = length;
}
}
EXPORT_SYMBOL_NS_GPL(cxl_probe_component_regs, CXL);
CXL.cache and CXL.mem Architectural Register Header
CXL HDM Decoder Capability Header
CXL HDM Decoder Capability Structure
4. cxl_probe_device_regs()
// 探测 CXL 设备寄存器块,记录位置及大小
// dev : CXL 设备内核对象
// base : 寄存器块 CXL Device Register interface 所在位置的基地址,已映射后的虚拟地址
// map : 描述发现的寄存器块信息的对象
void cxl_probe_device_regs(struct device *dev, void __iomem *base,
struct cxl_device_reg_map *map)
{
int cap, cap_count;
u64 cap_array;
*map = (struct cxl_device_reg_map){ 0 };
cap_array = readq(base + CXLDEV_CAP_ARRAY_OFFSET);
// 由下图 Figure 138. CXL Memory Device Registers 知
// 偏移为 CXLDEV_CAP_ARRAY_OFFSET(0) 是 CXL Device Capabilities Array Register 寄存器
// Ref CXL 2.0 8.2.8 CXL Device Register Interface
if (FIELD_GET(CXLDEV_CAP_ARRAY_ID_MASK, cap_array) !=
CXLDEV_CAP_ARRAY_CAP_ID)
// Cap ID 必须为 CXLDEV_CAP_ARRAY_CAP_ID(0)
// 如下图 CXL Device Capabilities Array Register
// Ref CXL 2.0 CXL Device Capabilities Array Register : Capability ID
return;
cap_count = FIELD_GET(CXLDEV_CAP_ARRAY_COUNT_MASK, cap_array);
// 同样,获得元素 Capabilities 数量, [47:32], 每个元素头 16 字节,并前后连续
// cap_count 不包括 CXL Device Capability Header Register,第一个header
for (cap = 1; cap <= cap_count; cap++) {
// 进行遍历,从 1 开始,地址跳过 CXL Device Capabilities Array Register, 占 0x10 字节
struct cxl_reg_map *rmap;
u32 offset, length;
u16 cap_id;
cap_id = FIELD_GET(CXLDEV_CAP_HDR_CAP_ID_MASK,
readl(base + cap * 0x10));
offset = readl(base + cap * 0x10 + 0x4);
length = readl(base + cap * 0x10 + 0x8);
// 每个 Capabilities 长度 16字节
// Ref CXL 2.0r 8.2.8.1 CXL Device Capabilities Array Register : Capabilities Count
// 获取 cap_id[15:0], offset[63:32], lenth[95:64]
// Ref CXL 2.0r 8.2.8.2 CXL Device Capability Header Register
// or 如下图 CXL Device Capability Header Register
switch (cap_id) {
// 根据 id 做不同记录处理
// 不过都是记录偏移和大小以及生效标志
// 如下图 CXL Device Capabilities, 分别取 1, 2, 3
// CXL Memeory Device 必须要有 1 和 2
// Ref CXL 2.0 8.2.8.2.1 CXL Device Capabilities
case CXLDEV_CAP_CAP_ID_DEVICE_STATUS:
// 1
dev_dbg(dev, "found Status capability (0x%x)\n", offset);
rmap = &map->status;
break;
case CXLDEV_CAP_CAP_ID_PRIMARY_MAILBOX:
// 2
dev_dbg(dev, "found Mailbox capability (0x%x)\n", offset);
rmap = &map->mbox;
break;
case CXLDEV_CAP_CAP_ID_SECONDARY_MAILBOX:
// 3
dev_dbg(dev, "found Secondary Mailbox capability (0x%x)\n", offset);
break;
case CXLDEV_CAP_CAP_ID_MEMDEV:
// 4000h
// 8.2.8.5 Memory Device Registers
// CXL Memeory Device 必须要有
dev_dbg(dev, "found Memory Device capability (0x%x)\n", offset);
rmap = &map->memdev;
break;
default:
// 0000h - 3FFFFh 描述通用 CXL 设备能力
// 4000h - 7FFFFh 描述与PCI头(offset 09h)中 class code register 相关的特定能力
// 8000h - FFFFh 描述 vedndor specific capabilities
if (cap_id >= 0x8000)
dev_dbg(dev, "Vendor cap ID: %#x offset: %#x\n", cap_id, offset);
else
dev_dbg(dev, "Unknown cap ID: %#x offset: %#x\n", cap_id, offset);
break;
}
if (!rmap)
continue;
// 保存 id, offset, length 到 map 相应字段
rmap->valid = true;
rmap->id = cap_id;
rmap->offset = offset;
rmap->size = length;
}
}
EXPORT_SYMBOL_NS_GPL(cxl_probe_device_regs, CXL);
CXL Memory Device Registers
CXL Device Capabilities Array Register
CXL Device Capability Header Register
CXL Device Capabilities
5. cxl_map_device_regs()
// 映射设备寄存器,.io 寄存器块
int cxl_map_device_regs(struct pci_dev *pdev,
struct cxl_device_regs *regs,
struct cxl_register_map *map)
{
struct device *host = map->host;
resource_size_t phys_addr = map->resource;
// phys_addr 是 CXL memory device 寄存器组的物理首地址
struct mapinfo {
const struct cxl_reg_map *rmap;
void __iomem **addr;
} mapinfo[] = {
{ &map->device_map.status, ®s->status, },
{ &map->device_map.mbox, ®s->mbox, },
{ &map->device_map.memdev, ®s->memdev, },
};
// 组成一个数组,方便管理
int i;
for (i = 0; i < ARRAY_SIZE(mapinfo); i++) {
struct mapinfo *mi = &mapinfo[i];
resource_size_t length;
resource_size_t addr;
if (!mi->rmap->valid)
continue;
// 对于有效的 CXL device capability register
addr = phys_addr + mi->rmap->offset;
length = mi->rmap->size;
// 获取偏移位置和大小
*(mi->addr) = devm_cxl_iomap_block(host, addr, length);
// devm_cxl_iomap_block 申请空间 [addr, length] 并进行映射,返回虚拟地址保存
if (!*(mi->addr))
return -ENOMEM;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(cxl_map_device_regs, CXL);
6. cxl_count_regblock() / cxl_find_regblock_instance()
int cxl_count_regblock(struct pci_dev *pdev, enum cxl_regloc_type type)
{
struct cxl_register_map map;
int rc, count = 0;
while (1) {
// 主要函数
rc = cxl_find_regblock_instance(pdev, type, &map, count);
if (rc)
return count;
count++;
}
}
EXPORT_SYMBOL_NS_GPL(cxl_count_regblock, CXL);
/**
* cxl_find_regblock_instance() - Locate a register block by type / index
* @pdev: The CXL PCI device to enumerate.
* @type: Register Block Indicator id
* @map: Enumeration output, clobbered on error
* @index: Index into which particular instance of a regblock wanted in the
* order found in register locator DVSEC.
*
* Return: 0 if register block enumerated, negative error code otherwise
*
* A CXL DVSEC may point to one or more register blocks, search for them
* by @type and @index.
*/
int cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_type type,
struct cxl_register_map *map, int index)
{
u32 regloc_size, regblocks;
int instance = 0;
int regloc, i;
*map = (struct cxl_register_map) {
.host = &pdev->dev,
.resource = CXL_RESOURCE_NONE,
};
// 寻找 DVSEC REG LOCATOR
// 通过 Table 124 CXL DVSEC ID Assignment DVSEC ID 表可知 Register Locator DVSEC 为 8
// Register Locator DVSEC 指示寄存器块的类型和位置
// CXL_REGLOC_RBI_PMU 表示 CPMU Registers,在 CXL 2.0r 8.1.9.1 Register Offset Low 15:8 定义
// #define CXL_DVSEC_REG_LOCATOR 8
regloc = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
CXL_DVSEC_REG_LOCATOR);
if (!regloc)
return -ENXIO;
pci_read_config_dword(pdev, regloc + PCI_DVSEC_HEADER1, ®loc_size);
regloc_size = FIELD_GET(PCI_DVSEC_HEADER1_LENGTH_MASK, regloc_size);
regloc += CXL_DVSEC_REG_LOCATOR_BLOCK1_OFFSET;
regblocks = (regloc_size - CXL_DVSEC_REG_LOCATOR_BLOCK1_OFFSET) / 8;
// #define CXL_DVSEC_REG_LOCATOR_BLOCK1_OFFSET 0xC
// Register Locator DVSEC 布局是 0xc 字节的头 + (n * 8) n是寄存器块的个数
// 遍历寄存器块并根据第二个参数 type 去匹配
// 同时,对map进行赋值,可获取寄存器块的类型、在哪个BAR空间以及偏移位置、寄存器块最大值
// 对于 CXL1.1设备,componenet 寄存器不在bar空间中,需要在RCRB中寻找
for (i = 0; i < regblocks; i++, regloc += 8) {
u32 reg_lo, reg_hi;
pci_read_config_dword(pdev, regloc, ®_lo);
pci_read_config_dword(pdev, regloc + 4, ®_hi);
if (!cxl_decode_regblock(pdev, reg_lo, reg_hi, map))
continue;
if (map->reg_type == type) {
if (index == instance)
return 0;
instance++;
}
}
map->resource = CXL_RESOURCE_NONE;
return -ENODEV;
}
EXPORT_SYMBOL_NS_GPL(cxl_find_regblock_instance, CXL);