linux 6.10.0 CXL/pci.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》

正文

Path: linux/drivers/cxl/pci.c

1. id_table 定义

static const struct pci_device_id cxl_mem_pci_tbl[] = {
	/* PCI class code for CXL.mem Type-3 Devices */
	{ PCI_DEVICE_CLASS((PCI_CLASS_MEMORY_CXL << 8 | CXL_MEMORY_PROGIF), ~0)},
	{ /* terminate list */ },

#define PCI_CLASS_MEMORY_CXL		0x0502
#define CXL_MEMORY_PROGIF	0x10
};

2. id匹配,调用 probe 函数

static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
	struct pci_host_bridge *host_bridge = pci_find_host_bridge(pdev->bus);
	struct cxl_memdev_state *mds;
    // CXL 设备
	struct cxl_dev_state *cxlds;
	struct cxl_register_map map;
    // CXL memory 设备
	struct cxl_memdev *cxlmd;
	int i, rc, pmu_count;
	bool irq_avail;

	/*
	 * Double check the anonymous union trickery in struct cxl_regs
	 * FIXME switch to struct_group()
	 */
	BUILD_BUG_ON(offsetof(struct cxl_regs, memdev) !=
		     offsetof(struct cxl_regs, device_regs.memdev));

	rc = pcim_enable_device(pdev);
    // pcim_enable_device 使能设备
    // pcim 是托管版本,自动调用 pci_disable_device
    // 1. 设置设备电源状态 Set the power state of a PCI device to D0
    // 2. 如果设备连接桥,配置桥
    // 3. 通过设置PCIE设备的 Command register 1:0 使能设备资源,IO或者MEM
    // 4. 如果 MSI/MSIx 都没有使能,Command register 寄存器禁止了 interrupt pin, 那么解除禁止
	if (rc)
		return rc;
	pci_set_master(pdev);
    // pci_set_master 使能给定 PCI 设备的 Bus Mastering 功能。Bus Mastering 是一种允许连接到总线的设备(如 PCI 设备)直接访问系统内存,无需通过 CPU 的机制
    // 1. 设置 command register 寄存器的 Bus Master Enable
    // 2. Call pcibios_set_master 设置设备 Latency Timer Register(0xd) 寄存器
    //    做一些体系结构特定的总线主控设置的。它检查并可能会调整PCI设备的延迟定时器值以避免PCI总线拥塞。
	mds = cxl_memdev_state_create(&pdev->dev);
    // Call core.mbox.c 
    // 申请并初始化  cxl_memdev_state *mds
	if (IS_ERR(mds))
		return PTR_ERR(mds);
	cxlds = &mds->cxlds;
	pci_set_drvdata(pdev, cxlds);
    // 为设备保存驱动资源 cxlds
    // dev->driver_data = data  -> pdev->dev->driver_data = cxlds

	cxlds->rcd = is_cxl_restricted(pdev);
    // pdev->dev 缓存的有设备 PCIe Capabilities Register 的16bit值
    // PCI Express Capabilities Register (Offset 02h) bit 7:4 表示设备的类型,1001b 为 RCiEP
    // Assume that any RCIEP that emits the CXL memory expander class code is an RCD

	cxlds->serial = pci_get_dsn(pdev);
    // 获取设备的序列号, 64bit
    // PCIe 6.0r 7.9.3 Device Serial Number Extended Capability
    // PCI Express Extended Capability ID 0003h

    
	cxlds->cxl_dvsec = pci_find_dvsec_capability(
		pdev, PCI_VENDOR_ID_CXL, CXL_DVSEC_PCIE_DEVICE);
    // pci_find_dvsec_capability 获取PCIE 的DVSEC寄存器偏移, 其他地方介绍此函数
    // #define PCI_VENDOR_ID_CXL		0x1e98
    // #define CXL_DVSEC_PCIE_DEVICE	0
    // DVSEC 其实就是一组特殊的 PCIe capability register, vendor 为 0x1e98
    // CXL 2.0r Table 124 CXL DVSEC ID Assignment DVSEC ID 表
    //          8.1.3 PCIe DVSEC for CXL Device
    // 这个寄存器主要是CXL设备的一些控制状态和内存大小的信息
	if (!cxlds->cxl_dvsec)
		dev_warn(&pdev->dev,
			 "Device DVSEC not present, skip CXL.mem init\n");

	rc = cxl_pci_setup_regs(pdev, CXL_REGLOC_RBI_MEMDEV, &map);
    // CXL_REGLOC_RBI_MEMDEV == 3
    // 通过 Table 124 CXL DVSEC ID Assignment DVSEC ID 表可知 Register Locator DVSEC 为 8
    // Register Locator DVSEC 指示寄存器块的类型和位置
    // CXL_REGLOC_RBI_MEMDEV 表示CXL内存扩展设备寄存器,在 CXL 2.0r 8.1.9.1 Register Offset Low 15:8 定义
    // Register Locator DVSEC 布局是 0xc 字节的头 + (n * 8) n是寄存器块的个数
    // 遍历寄存器块并根据第二个参数 CXL_REGLOC_RBI_MEMDEV 去匹配
    // 同时,对map进行赋值,可获取寄存器块的类型、在哪个BAR空间以及偏移位置、寄存器块最大值
    // 对于 CXL1.1设备,componenet 寄存器不在bar空间中,需要在RCRB中寻找
    // 对寻找的寄存器块地址映射到虚拟空间 map->base 虚拟首地址, 映射大小到所在 bar 末尾
    // 映射完毕之后,cxl_probe_regs() 对不同的寄存器块类型进行解析, map 中有一个 union, 对其进行赋值
    // 该函数在其他地方解析
    // 对于 CXL_REGLOC_RBI_MEMDEV 获取 CXL memory device 的相关必须实现的寄存器,如 Device Status Registers, Primary Mailbox Registers 等
    // cxl_pci_setup_regs 填充了 map 的以下字段
    // struct cxl_register_map {
    //     struct device *host;   CXL 设备对象
    //     void __iomem *base;    寄存器块映射的虚拟首地址
    //     resource_size_t resource;  寄存器块的物理首地址
    //     resource_size_t max_size;  寄存器块的最大大小,到 bar 空间尾部
    //     u8 reg_type;        寄存器块的类型 CXL_REGLOC_RBI_MEMDEV
    //     union {
    //         struct cxl_component_reg_map component_map;
    //         struct cxl_device_reg_map device_map;    填充相应的寄存器信息, Device Status Registers, Primary Mailbox Registers 地址和大小
    //         struct cxl_pmu_reg_map pmu_map;
    //     };
    // };
	if (rc)
		return rc;
	
	rc = cxl_map_device_regs(&map, &cxlds->regs.device_regs);
    // 对获取的 map->device_map 的 status 、mbox 、memdev进行映射,获取虚拟地址空间,保存在 cxlds->regs.device_regs 的三个地址中 *status, *mbox, *memdev
	if (rc)
		return rc;
	
	/*
	 * If the component registers can't be found, the cxl_pci driver may
	 * still be useful for management functions so don't return an error.
	 */
	rc = cxl_pci_setup_regs(pdev, CXL_REGLOC_RBI_COMPONENT,
				&cxlds->reg_map);
    // 获取 Component Registers 的地址和大小
	if (rc)
		dev_warn(&pdev->dev, "No component registers (%d)\n", rc);
	else if (!cxlds->reg_map.component_map.ras.valid)
		dev_dbg(&pdev->dev, "RAS registers not found\n");
	
	rc = cxl_map_component_regs(&cxlds->reg_map, &cxlds->regs.component,
				    BIT(CXL_CM_CAP_CAP_ID_RAS));
    // 同上,映射 map->component_map 的 hdm_decoder 和 ras,获取虚拟地址
    // 第三个参数表示选择映射哪一个
	if (rc)
		dev_dbg(&pdev->dev, "Failed to map RAS capability.\n");
	
	rc = cxl_await_media_ready(cxlds);
    // 检查相应寄存器,内存是否准备好
	if (rc == 0)
		cxlds->media_ready = true;
	else
		dev_warn(&pdev->dev, "Media not active (%d)\n", rc);

	irq_avail = cxl_alloc_irq_vectors(pdev);
    // 申请 MSI/MSI-X 中断, 1 - 16 个, 每个 function 一个

	rc = cxl_pci_setup_mailbox(mds, irq_avail);
    // 检查邮箱是否准备好,获取邮箱的负载大小,添加发送函数,注册邮箱接收中断函数
	if (rc)
		return rc;

	rc = cxl_enumerate_cmds(mds);
    // 通过CEL 获取支持的命令,并置相关位记录一下是否使能
	if (rc)
		return rc;

	rc = cxl_set_timestamp(mds);
    // 发送 CXL_MBOX_OP_SET_TIMESTAMP 命令设置时间戳
    // CXL_MBOX_OP_SET_TIMESTAMP	= 0x0301
    // 获取当前系统时间,设置给设备
	if (rc)
		return rc;
	
	rc = cxl_poison_state_init(mds);
    // 如果使能了 CXL_POISON_ENABLED_LIST
    // 初始化锁与分配  mds->poison.list_out 缓冲区
	if (rc)
		return rc;

	rc = cxl_dev_state_identify(mds);
    // 发送 CXL_MBOX_OP_IDENTIFY 命令,返回CXL内存设备的基础信息
    // CXL 3.1r 8.2.9.9.1 Identify Memory Device
    // 获取设备的 mds->total_bytes, volatile_only_bytes, persistent_only_bytes, partition_align_bytes, lsa_size, firmware_version
    // mds->poison.max_errors
	if (rc)
		return rc;

	rc = cxl_mem_create_range_info(mds);
    // 利用上述获取的信息,创建内存资源信息
    // mds->partition_align_bytes 分区对齐:如果设备具有可用作易失性存储器或持久性存储器的容量,则此字段指示分区对齐大小,
    //                            可分区容量等于总容量 - 仅易失性容量 - 仅持久性容量,
    //                            如果为 0,则设备不支持将容量划分为易失性容量和持久性容量。
    // 根据 mds->volatile_only_bytes 申请名字为 ram 的内存资源 request_resource,信息保存在 cxlds->ram_res
    // 根据 mds->persistent_only_bytes 申请名字为 pmem 的内存资源 request_resource,信息保存在 cxlds->pmem_res
    // 如果 partition_align_bytes不为0,则需要分区,用 CXL_MBOX_OP_GET_PARTITION_INFO 获取分区大小信息
    // CXL_MBOX_OP_GET_PARTITION_INFO	= 0x4100
    // CXL 3.1r 8.2.9.9.2.1 Get Partition Info
    // cxl_await_media_ready() 已经检查过每个HDM Memory_Info_Valid flag 为 1, 表示分区信息有效
	if (rc)
		return rc;

	cxlmd = devm_cxl_add_memdev(&pdev->dev, cxlds);
	// 申请资源并初始化 cxlmd
    // 创建字符设备驱动
	if (IS_ERR(cxlmd))
		return PTR_ERR(cxlmd);
	rc = devm_cxl_setup_fw_upload(&pdev->dev, mds);
    // 主要通过 firmware_upload_register 注册用户层接口,调用相应的接口,详情 memdev.c
	if (rc)
		return rc;

	rc = devm_cxl_sanitize_setup_notifier(&pdev->dev, cxlmd);
    // sys节点mds->security.sanitize_node 
    // 用户层可通过epoll,select来监听事件变化
    // opcode 为 CXL_MBOX_OP_SANITIZE 时,唤醒工作队列 mds->security.poll_dwork

	if (rc)
		return rc;

	pmu_count = cxl_count_regblock(pdev, CXL_REGLOC_RBI_PMU);
    // 获取 CPMU 寄存器信息,个数 pmu_count,物理首地址map->resource,大小map->max_size等
    // CPMU 允许多个实例
    // CPMU: CXL Performance Monitoring Unit
	for (i = 0; i < pmu_count; i++) {
		struct cxl_pmu_regs pmu_regs;

        // 寻找 第 i 个实例,记录在 map 中
		rc = cxl_find_regblock_instance(pdev, CXL_REGLOC_RBI_PMU, &map, i);
		if (rc) {
			dev_dbg(&pdev->dev, "Could not find PMU regblock\n");
			break;
		}

		rc = cxl_map_pmu_regs(&map, &pmu_regs);
        // map->resource 是物理首地址
        // 申请资源并进行虚拟地址映射,保存在 pmu_regs->pmu
		if (rc) {
			dev_dbg(&pdev->dev, "Could not map PMU regs\n");
			break;
		}

		rc = devm_cxl_pmu_add(cxlds->dev, &pmu_regs, cxlmd->id, i, CXL_PMU_MEMDEV);
        // 填充 pmu_regs, 设备初始化,添加名为 cxl_pmu(与驱动匹配) 的设备 pmu_mem%d.%d
        // 设备挂载 cxl_bus 上
		if (rc) {
			dev_dbg(&pdev->dev, "Could not add PMU instance\n");
			break;
		}
	}

	rc = cxl_event_config(host_bridge, mds, irq_avail);
    // 检查事件记录,申请中断
    // 获取所有记录,并清空
	if (rc)
		return rc;

    // 根据设备设置,设置 RAS 相关 mask 寄存器
	rc = cxl_pci_ras_unmask(pdev);
	if (rc)
		dev_dbg(&pdev->dev, "No RAS reporting unmasked\n");

    // 保存设备状态
	pci_save_state(pdev);

	return rc;
}

PCIe DVSEC for CXL Device
请添加图片描述

3. cxl_await_media_ready()


int cxl_await_media_ready(struct cxl_dev_state *cxlds)
{
	struct pci_dev *pdev = to_pci_dev(cxlds->dev);
	int d = cxlds->cxl_dvsec;
	int rc, i, hdm_count;
	u64 md_status;
	u16 cap;

	rc = pci_read_config_word(pdev,
				  d + CXL_DVSEC_CAP_OFFSET, &cap);
    // d 是 PCIe DVSEC for CXL Device 的首地址
    // #define   CXL_DVSEC_CAP_OFFSET		0xA
    // 上图, PCIe DVSEC for CXL Device 或者下图 DVSEC CXL Capability,可知偏移 0xA 是 CXL Capability
	if (rc)
		return rc;

	hdm_count = FIELD_GET(CXL_DVSEC_HDM_COUNT_MASK, cap);
    // #define     CXL_DVSEC_HDM_COUNT_MASK	GENMASK(5, 4)	
    // 下图 DVSEC CXL Capability, 5:4 表示 HDM 的数量
	for (i = 0; i < hdm_count; i++) {
		rc = cxl_dvsec_mem_range_valid(cxlds, i);
        // 检查参数 i 指定的 range 寄存器是否可用,Range Size Low 寄存器中有字段标明此状态
		if (rc)
			return rc;
	}

	for (i = 0; i < hdm_count; i++) {
		rc = cxl_dvsec_mem_range_active(cxlds, i);
        // 检查参数 i 指定的 range 内存是否初始化完成并可以被软件使用,Range Size Low 寄存器中 bit1 标明此状态
        // 超时时间全局变量 media_ready_timeout, 可通过模块参数修改
        // #define     CXL_DVSEC_MEM_ACTIVE	BIT(1)
		if (rc)
			return rc;
	}


	md_status = readq(cxlds->regs.memdev + CXLMDEV_STATUS_OFFSET);
    // #define CXLMDEV_STATUS_OFFSET 0x0
    // 如下图 Memory Device Status Register
    // CXL 2.0r 8.2.8.5.1.1 Memory Device Status Register
    // 
	if (!CXLMDEV_READY(md_status))
    // 检查 md_status 3:2 是不是等于 1
    // 为 1 表示准备好
		return -EIO;

	return 0;
}
EXPORT_SYMBOL_NS_GPL(cxl_await_media_ready, CXL);

DVSEC CXL Capability
请添加图片描述

Memory Device Status Register
请添加图片描述

4. cxl_dvsec_mem_range_valid

检查参数 id 指定的 range 寄存器是否可用,Range Size Low 寄存器中有字段标明此状态

static int cxl_dvsec_mem_range_valid(struct cxl_dev_state *cxlds, int id)
{
	struct pci_dev *pdev = to_pci_dev(cxlds->dev);
	int d = cxlds->cxl_dvsec;
	bool valid = false;
	int rc, i;
	u32 temp;

	if (id > CXL_DVSEC_RANGE_MAX)
		return -EINVAL;

	/* Check MEM INFO VALID bit first, give up after 1s */
	i = 1;
	do {
		rc = pci_read_config_dword(pdev,
					   d + CXL_DVSEC_RANGE_SIZE_LOW(id),
					   &temp);
        // #define   CXL_DVSEC_RANGE_SIZE_LOW(i)	(0x1C + (i * 0x10))
        // 由上图 PCIe DVSEC for CXL Device 可知,第一个 DVSEC CXL Range Size Low 偏移是 0x1C
        // 后面的每个 DVSEC CXL Range1 Size Low 都相隔 0x10
		if (rc)
			return rc;

		valid = FIELD_GET(CXL_DVSEC_MEM_INFO_VALID, temp);
        // #define     CXL_DVSEC_MEM_INFO_VALID	BIT(0)
        // 下图 DVSEC CXL Range1 Size Low (Offset 1Ch), bit0 表示CXL range Size high and size low register valid
        // CXL 设备复位 1 S 之内必须置 1
		if (valid)
			break;
		msleep(1000);
	} while (i--);

	if (!valid) {
		dev_err(&pdev->dev,
			"Timeout awaiting memory range %d valid after 1s.\n",
			id);
		return -ETIMEDOUT;
	}

	return 0;
}

DVSEC CXL Range1 Size Low (Offset 1Ch)
请添加图片描述

5. cxl_pci_setup_mailbox


static int cxl_pci_setup_mailbox(struct cxl_memdev_state *mds, bool irq_avail)
{
	struct cxl_dev_state *cxlds = &mds->cxlds;
    // #define CXLDEV_MBOX_CAPS_OFFSET 0x00
    // CXL 2.0r 8.2.8.4.3 Mailbox Capabilities Register
	const int cap = readl(cxlds->regs.mbox + CXLDEV_MBOX_CAPS_OFFSET);
	struct device *dev = cxlds->dev;
	unsigned long timeout;
	int irq, msgnum;
	u64 md_status;
	u32 ctrl;
    
    // mbox_ready_timeout 全局变量,可配置时间,默认60S
	timeout = jiffies + mbox_ready_timeout * HZ;
    // 首先查询设备是否准备好mailbox
	do {
		md_status = readq(cxlds->regs.memdev + CXLMDEV_STATUS_OFFSET);
        // Ref CXL 2.0 Spec 8.2.8.5.1.1 Memory Device Status Register
		// or 下图 Mailbox Interfaces Ready
		// bit4 置1表示设备已经准备好通过 mailbox 接收命令了
		// #define   CXLMDEV_MBOX_IF_READY BIT(4)
		if (md_status & CXLMDEV_MBOX_IF_READY)
			break;
		if (msleep_interruptible(100))
			break;
	} while (!time_after(jiffies, timeout));

    // 如果是超时,返回错误
	if (!(md_status & CXLMDEV_MBOX_IF_READY)) {
		cxl_err(dev, md_status, "timeout awaiting mailbox ready");
		return -ETIMEDOUT;
	}

	/*
	 * A command may be in flight from a previous driver instance,
	 * think kexec, do one doorbell wait so that
	 * __cxl_pci_mbox_send_cmd() can assume that it is the only
	 * source for future doorbell busy events.
	 */
     // 等待 mailbox 空闲
	if (cxl_pci_mbox_wait_for_doorbell(cxlds) != 0) {
		cxl_err(dev, md_status, "timeout awaiting mailbox idle");
		return -ETIMEDOUT;
	}
    // 发送邮箱命令接口
	mds->mbox_send = cxl_pci_mbox_send;
    // #define   CXLDEV_MBOX_CAP_PAYLOAD_SIZE_MASK GENMASK(4, 0)
    // 8.2.8.4.3 Mailbox Capabilities Register 4:0
    // 命令负载大小,字节, 2的n次幂,最小 256 字节(8),最大 1MB(20)
	mds->payload_size =
		1 << FIELD_GET(CXLDEV_MBOX_CAP_PAYLOAD_SIZE_MASK, cap);
    
	// Payload Size: Size of the Command Payload Registers in bytes, expressed as 2^n.
	// The minimum size is 256 bytes (n=8) and the maximum size is 1 MB (n=20).
	// Ref CXL 2.0 8.2.8.4.3 Mailbox Capabilities Register

	/*
	 * CXL 2.0 8.2.8.4.3 Mailbox Capabilities Register
	 *
	 * If the size is too small, mandatory commands will not work and so
	 * there's no point in going forward. If the size is too large, there's
	 * no harm is soft limiting it.
	 */
	mds->payload_size = min_t(size_t, mds->payload_size, SZ_1M);
	if (mds->payload_size < 256) {
		dev_err(dev, "Mailbox is too small (%zub)",
			mds->payload_size);
		return -ENXIO;
	}

	dev_dbg(dev, "Mailbox payload sized %zu", mds->payload_size);
    // 驱动工作用的变量和工作队列
	rcuwait_init(&mds->mbox_wait);
	INIT_DELAYED_WORK(&mds->security.poll_dwork, cxl_mbox_sanitize_work);

	/* background command interrupts are optional */
    // #define   CXLDEV_MBOX_CAP_BG_CMD_IRQ BIT(6)
    // 置1表示当后台完成一个命令时设备支持 MSI/MSI-X 中断
    // scondary mailbox 应该为 0
	if (!(cap & CXLDEV_MBOX_CAP_BG_CMD_IRQ) || !irq_avail)
		return 0;

    // #define   CXLDEV_MBOX_CAP_IRQ_MSGNUM_MASK GENMASK(10, 7)
    // 10:7 是这个邮箱实例使用的 MSI/MSI-X 向量
    // MSI 使能,硬件更新这个 field 的值为 MSI 消息的 number
    // MSI-X 使能,表示第一个16 entries 中的一个 entry
    // 如果 MSI 和 MSI-X 都实现了,软件都使能了,这个 field 的值未定义
	msgnum = FIELD_GET(CXLDEV_MBOX_CAP_IRQ_MSGNUM_MASK, cap);
	irq = pci_irq_vector(to_pci_dev(cxlds->dev), msgnum);
	if (irq < 0)
		return 0;
    // 申请中断 irq, 中断处理函数 cxl_pci_mbox_irq
	if (cxl_request_irq(cxlds, irq, cxl_pci_mbox_irq))
		return 0;

	dev_dbg(cxlds->dev, "Mailbox interrupts enabled\n");
	/* enable background command mbox irq support */
    // CXL 2.0r 8.2.8.4.4 Mailbox Control Register
    // #define CXLDEV_MBOX_CTRL_OFFSET 0x04
	ctrl = readl(cxlds->regs.mbox + CXLDEV_MBOX_CTRL_OFFSET);
    // #define   CXLDEV_MBOX_CTRL_BG_CMD_IRQ BIT(2)
    // 如果后台命令完成中断支持,软件置 1 使能功能
	ctrl |= CXLDEV_MBOX_CTRL_BG_CMD_IRQ;
	writel(ctrl, cxlds->regs.mbox + CXLDEV_MBOX_CTRL_OFFSET);

	return 0;
}

Mailbox Interfaces Ready
请添加图片描述

6. cxl_mbox_sanitize_work()


static void cxl_mbox_sanitize_work(struct work_struct *work)
{
	struct cxl_memdev_state *mds =
		container_of(work, typeof(*mds), security.poll_dwork.work);
	struct cxl_dev_state *cxlds = &mds->cxlds;

	mutex_lock(&mds->mbox_mutex);
	if (cxl_mbox_background_complete(cxlds)) {
        // 如果时后台 100% 完成
		mds->security.poll_tmo_secs = 0;
		if (mds->security.sanitize_node)
            // 通知用户层
			sysfs_notify_dirent(mds->security.sanitize_node);
		mds->security.sanitize_active = false;

		dev_dbg(cxlds->dev, "Sanitization operation ended\n");
	} else {
		int timeout = mds->security.poll_tmo_secs + 10;

		mds->security.poll_tmo_secs = min(15 * 60, timeout);
        // poll_tmo_secs 一开始是 1, 超时设置 11 S, 最大 15 * 60 S, 检查是否完成
		schedule_delayed_work(&mds->security.poll_dwork, timeout * HZ);
	}
	mutex_unlock(&mds->mbox_mutex);
}

7.cxl_event_config()


static int cxl_event_config(struct pci_host_bridge *host_bridge,
			    struct cxl_memdev_state *mds, bool irq_avail)
{
	struct cxl_event_interrupt_policy policy;
	int rc;

	/*
	 * When BIOS maintains CXL error reporting control, it will process
	 * event records.  Only one agent can do so.
	 */
     // 如果 BIOS 维护错误报告,它将处理事件记录,只能有一个 agent
	if (!host_bridge->native_cxl_error)
		return 0;

	if (!irq_avail) {
		dev_info(mds->cxlds.dev, "No interrupt support, disable event processing.\n");
		return 0;
	}

	rc = cxl_mem_alloc_event_buf(mds);
    // 申请内存空间, mds->payload_size大小,首地址 mds->event.buf 
	if (rc)
		return rc;

	rc = cxl_event_get_int_policy(mds, &policy);
    // 发送 mailbox CXL_MBOX_OP_GET_EVT_INT_POLICY
    // CXL_MBOX_OP_GET_EVT_INT_POLICY	= 0x0102
    // Get Event Interrupt Policy ref CXL 3.1r 8.2.9.2.4 Get Event Interrupt Policy
    // 获取设备事件的中断设置
	if (rc)
		return rc;

	if (cxl_event_int_is_fw(policy.info_settings) ||
	    cxl_event_int_is_fw(policy.warn_settings) ||
	    cxl_event_int_is_fw(policy.failure_settings) ||
	    cxl_event_int_is_fw(policy.fatal_settings)) {
        // 如下图 Get Event Interrupt Policy Output Payload, 获取相应寄存器信息
        // 任意一个设置为 CXL_INT_FW == 2, 则报错
		dev_err(mds->cxlds.dev,
			"FW still in control of Event Logs despite _OSC settings\n");
		return -EBUSY;
	}

	rc = cxl_event_irqsetup(mds);
    // cxl_event_config_msgnums() 使用 CXL_MBOX_OP_SET_EVT_INT_POLICY 命令设置事件中断 MSI/MSI-X
    // CXL_MBOX_OP_SET_EVT_INT_POLICY	= 0x0103
    // 然后获取中断号,并申请中断,处理函数为 cxl_event_thread
	if (rc)
		return rc;

	cxl_mem_get_event_records(mds, CXLDEV_EVENT_STATUS_ALL);
    // #define CXLDEV_EVENT_STATUS_ALL 0xF
	return 0;
}

Get Event Interrupt Policy Output Payload
请添加图片描述

8. cxl_pci_ras_unmask()


static int cxl_pci_ras_unmask(struct pci_dev *pdev)
{	
	struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
	void __iomem *addr;
	u32 orig_val, val, mask;
	u16 cap;
	int rc;

	if (!cxlds->regs.ras) {
		dev_dbg(&pdev->dev, "No RAS registers.\n");
		return 0;
	}

	/* BIOS has PCIe AER error control */
	if (!pcie_aer_is_native(pdev))
		return 0;
    
    // #define PCI_EXP_DEVCTL		0x08
    // PCIE 配置空间寄存器, 如下图 Device Control Register
	rc = pcie_capability_read_word(pdev, PCI_EXP_DEVCTL, &cap);
	if (rc)
		return rc;
    
    // #define  PCI_EXP_DEVCTL_URRE	0x0008
    // 下图 bit3 Unsupported Request Reporting Enable
	if (cap & PCI_EXP_DEVCTL_URRE) {
		addr = cxlds->regs.ras + CXL_RAS_UNCORRECTABLE_MASK_OFFSET;
		orig_val = readl(addr);

		mask = CXL_RAS_UNCORRECTABLE_MASK_MASK |
		       CXL_RAS_UNCORRECTABLE_MASK_F256B_MASK;
        // 两个 bit 清 0 ?
		val = orig_val & ~mask;
		writel(val, addr);
		dev_dbg(&pdev->dev,
			"Uncorrectable RAS Errors Mask: %#x -> %#x\n",
			orig_val, val);
	}

	if (cap & PCI_EXP_DEVCTL_CERE) {
        // #define  PCI_EXP_DEVCTL_CERE	0x0001	/* Correctable Error Reporting En. */
        // 下图 bit 0
		addr = cxlds->regs.ras + CXL_RAS_CORRECTABLE_MASK_OFFSET;
		orig_val = readl(addr);
		val = orig_val & ~CXL_RAS_CORRECTABLE_MASK_MASK;
		writel(val, addr);
		dev_dbg(&pdev->dev, "Correctable RAS Errors Mask: %#x -> %#x\n",
			orig_val, val);
	}

	return 0;
}

Device Control Register
请添加图片描述

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 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、付费专栏及课程。

余额充值