文章目录
前言
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