linux 6.10.0 CXL/reg.c 详解

前言

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, &regs->status, },
		{ &map->device_map.mbox, &regs->mbox, },
		{ &map->device_map.memdev, &regs->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, &regloc_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, &reg_lo);
		pci_read_config_dword(pdev, regloc + 4, &reg_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);

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Call Me Gavyn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值