文章目录
- 前言
- Ref
- 正文
- 1. cxl_setup_regs
- 2. cxl_probe_regs()
- 3. cxl_probe_component_regs()
- 4. cxl_probe_device_regs()
- 5. cxl_map_device_regs()
- 6. cxl_count_regblock() / cxl_find_regblock_instance()
前言
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 Registercomp_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 1if (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 Registersswitch (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 decodersdecoder_cnt = cxl_hdm_decoder_count(hdr);// 8.2.5.12 CXL HDM Decoder Capability Structure// 长度计算公式,设备的是 0x20 * decoder_cnt + 0x10length = 0x20 * decoder_cnt + 0x10;rmap = &map->hdm_decoder;break;// ID 为 2case CXL_CM_CAP_CAP_ID_RAS:// #define CXL_CM_CAP_CAP_ID_RAS 0x2// 8.2.5.2 CXL RAS Capability Headerdev_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 Interfaceif (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 IDreturn;cap_count = FIELD_GET(CXLDEV_CAP_ARRAY_COUNT_MASK, cap_array);// 同样,获得元素 Capabilities 数量, [47:32], 每个元素头 16 字节,并前后连续// cap_count 不包括 CXL Device Capability Header Register,第一个headerfor (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 Registerswitch (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 Capabilitiescase CXLDEV_CAP_CAP_ID_DEVICE_STATUS:// 1dev_dbg(dev, "found Status capability (0x%x)\n", offset);rmap = &map->status;break;case CXLDEV_CAP_CAP_ID_PRIMARY_MAILBOX:// 2dev_dbg(dev, "found Mailbox capability (0x%x)\n", offset);rmap = &map->mbox;break;case CXLDEV_CAP_CAP_ID_SECONDARY_MAILBOX:// 3dev_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 capabilitiesif (cap_id >= 0x8000)dev_dbg(dev, "Vendor cap ID: %#x offset: %#x\n", cap_id, offset);elsedev_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 registeraddr = 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 8regloc = 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);