feat: B7 APIC/IOAPIC接管 + NVMe写路径 + virtio_net virtqueue + PE兼容层修复 + QEMU测试套件全自动化

中断路由 (B7):
- apic.drv: LAPIC enable + IOAPIC重定向表编程(MADT ISO感知) + apic_route/apic_route_legacy/apic_diag开关
- UTSM: LAPIC EOI钩子(register_apic_eoi, ABI尾部追加) + IDT 0-255全覆盖 + spurious 0xFF特判
- FUCK: apic_route=0默认PIC(零回归) / =1 IOAPIC->LAPIC接管(失败完整降级) / legacy放开IRQ1/12/11

存储:
- nvme.drv: 写路径 + PRP list + 读回校验 + MSI/MSI-X中断驱动(nvme_msi开关, 回退纯轮询)
- build.ps1: apic.drv纳入构建(原为预置二进制)

网络:
- virtio_net: virtqueue RX/TX + netdev注册 + DHCP自测(slirp真实OFFER)

PE兼容层:
- pe_shim: guest->host指针转换(g_emu_mem_base + gp()) + shim fn32列补齐
- gen_pe.py: PE32+ RVA偏置修复(BUG-004) + cmd.exe生成
- desktop_app.h: da_mouse_poll先判AUX位 + da_mouse_init冲刷残留(BUG-003/008)
- shell: Ctrl修饰键跟踪(BUG-001) + g_run_image扩至1MB(BUG-002)

QEMU测试套件:
- 9用例全自动化(Run-All.ps1): boot-regression/driver-matrix/linux-compat/pe-compat/deaicup-e2e/userapps-matrix/network-e2e/performance-baseline/x86emu32-coverage
- QemuTest.ps1公共库: 串口断言/monitor sendkey/screendump/HTTP服务器/并发隔离

文档:
- CLAUDE.md: APIC/IOAPIC能力 + kernel_api暴露 + IRQ后端升级状态
- LESSONS_LEARNED.md: B7 LAPIC/IOAPIC经验教训(2条)
- Bug记录.txt: BUG-20260801-001~008 + BUG-20260803-009(9条)
- test-cases/*.md: 全部补充自动化脚本小节
- 桌面设计.md: .lnk快捷方式 + LINUXAPP.CNF Linux应用图标
- 系统开发策划.md: NVMe/virtio_net标完成 + B2/B7标已解决 + M2进度列
This commit is contained in:
xyou
2026-08-04 18:08:08 +08:00
parent f52157b7af
commit a26866e339
70 changed files with 4652 additions and 504 deletions
+970 -55
View File
@@ -1,4 +1,4 @@
/* DKM APIC Driver — APIC discovery + IOAPIC init + LAPIC enable
/* DKM APIC Driver — APIC discovery + IOAPIC init + LAPIC enable + 路由接管
* Stage 0, required, depends on "timer", provides "irq" and "apic".
*
* 真机路径:
@@ -8,6 +8,40 @@
* 3. MADT ISO (Interrupt Source Override) 处理: 按 ACPI 规范修正 IRQ→GSI 映射
* 4. SIPI 多核启动框架 (预留, 未实现)
*
* B7 路由接管 (apic_route):
* FUCK [drivers] apic_route=1 时, 本驱动在初始化末尾尝试把中断路由
* 从 8259 PIC 切换到 IOAPIC→LAPIC 路径:
* a. 经 kernel_api.register_apic_eoi 向 UTSM 注册 LAPIC EOI 钩子
* (B7 阶段2 接口; idt_handler 两个 IRQ 分支在分发末尾调用之)
* b. 注册 PIT tick 计数 handler 于 vector 0x20 — 即 idt_handler 的
* PIC 分支 (0x20-0x2F), 与键盘 0x21/鼠标 0x2C/网卡 0x2B 同一代码
* 路径; handler 返回 0 由钩子完成 LAPIC EOI, 重复投递 >=2 即证明
* "legacy vector 分支 + 钩子 EOI" 全链路可持续 (阶段1 用 0xE0 自
* EOI 只证明了 APIC 分支)
* c. 按 ISO 表解析 PIT GSI (QEMU q35: IRQ0→GSI2), 解屏蔽该条目
* d. LAPIC TPR=0 全放行, PIC IMR 全 1 屏蔽 (QEMU 无功能 IMCR 寄存器,
* PIC 全屏蔽即等效切换; 8259 与 IOAPIC 同时监听 IRQ 线, 全屏蔽避免双投递)
* e. PIT ch0 mode3 100Hz 产生真实硬件中断, 500ms 窗口内计数 >=2 次
* 即证明 IOAPIC→LAPIC→IDT→handler→钩子EOI 全链路
* f. 验证失败完整降级: IOAPIC 全 mask + 恢复原 PIC IMR + 注销 handler
* 与 EOI 钩子, 系统行为与切换前完全一致
*
* B7 阶段2 (apic_route_legacy):
* EOI 钩子落地后, legacy IRQ1(键盘 0x21)/IRQ12(鼠标 0x2C)/IRQ11(e1000
* 0x2B) 重定向项可持续投递, 不再是 one-shot。ISA 线按 MADT ISO 极性/触发
* (缺省 edge/active-high); PCI 线 (IRQ11) 无 ISO 覆盖时强制 level/
* active-low (PCI INTx 规范; QEMU 内部 pci irq 电平语义与之匹配,
* Linux/Windows 同配置)。e1000 handler 读 ICR 清源后电平 deassert,
* 由钩子 EOI 完成 LAPIC 侧清扫。
*
* apic_diag=1 诊断:
* - HPET 绝对时基对拍: 解析 ACPI HPET 表 (QEMU q35 有, 100MHz 主计数器),
* 用主计数器实测 tsc_calibrate 的 10ms 窗口与 route 验证的 500ms 窗口,
* 定论 "tick 2x" 根因 (TSC 校准测半 vs PIT mode3 双触发)
* - PS/2 鼠标流: 初始化 AUX 端口 + 注册 IRQ12 诊断 handler, 配合
* QEMU monitor mouse_move 验证 IRQ12 持续投递 (计数 + 观察窗)
* diag 只增观测不改变量路由语义, 默认 0 关闭。
*
* QEMU 兼容:
* - QEMU 默认只提供 1 个 IOAPIC, 24 条 GSI
* - 实机可能有多个 IOAPIC, GSI 范围更大
@@ -61,6 +95,11 @@ struct dkm_kernel_api {
const void *mmio;
void *(*mm_map_mmio)(u64 phys, u64 size);
void (*mm_unmap_mmio)(void *virt, u64 size);
/* B7 阶段2 尾部追加 (旧内核无此字段 -> 判空使用) */
void (*register_apic_eoi)(void (*eoi_fn)(void));
/* B7 阶段3 尾部追加 (本驱动不使用, 保持布局与 UTSM 一致) */
int (*irq_vector_alloc)(void);
void (*irq_vector_free)(int vector);
};
struct dkm_driver_handle;
@@ -95,7 +134,7 @@ const struct dkm_driver_desc driver_desc = {
.abi_version = DKM_ABI_VERSION,
.desc_size = sizeof(struct dkm_driver_desc),
.name = "apic",
.version = "0.1.0",
.version = "0.2.0",
.vendor = "Deshab",
.driver_class = 3, /* DKM_CLASS_INTERRUPT */
.stage = 0,
@@ -149,6 +188,45 @@ typedef struct __attribute__((packed)) {
u32 flags;
} acpi_madt;
/* ACPI HPET 表 (signature "HPET") — apic_diag 绝对时基对拍用 */
typedef struct __attribute__((packed)) {
acpi_sdt_header hdr;
u32 event_timer_block_id;
/* ACPI Generic Address Structure (GAS) */
u8 addr_space_id; /* 0 = system memory */
u8 register_bit_width;
u8 register_bit_offset;
u8 access_size;
u64 base_addr; /* HPET MMIO 物理基址 (QEMU: 0xFED00000) */
u8 hpet_number;
u16 min_clock_tick;
u8 page_protection;
} acpi_hpet;
/* Limine boot module 最小类型 (与 bootfs.drv / UTSM limine.h 布局一致) */
struct limine_file_min {
u64 revision;
void *address;
u64 size;
char *path;
char *cmdline;
u32 media_type;
u32 unused;
u32 tftp_ip;
u32 tftp_port;
u32 partition_index;
u32 mbr_disk_id;
u8 gpt_disk_uuid[16];
u8 gpt_part_uuid[16];
u8 part_uuid[16];
};
struct limine_module_response_min {
u64 revision;
u64 module_count;
struct limine_file_min **modules;
};
/* ---- IOAPIC 信息存储(真机多 IOAPIC 支持) ---- */
#define MAX_IOAPIC 4
@@ -186,7 +264,22 @@ struct madt_info {
struct lapic_entry lapics[MAX_LAPIC];
};
/* ---- B7 路由接管配置 (FUCK [drivers]) ---- */
struct apic_config {
int lapic_enable; /* lapic_enable, 默认 1 */
int ioapic_init; /* ioapic_init, 默认 1 */
int apic_route; /* apic_route, 默认 0 (PIC 路由, 现状) */
int apic_route_legacy; /* apic_route_legacy, 默认 0 */
int apic_diag; /* apic_diag, 默认 0 (HPET 校准/投递计数/鼠标流诊断) */
};
static const struct dkm_log_api *g_log;
static struct apic_config g_cfg = { 1, 1, 0, 0, 0 };
static volatile u32 *g_lapic; /* LAPIC MMIO 映射基址, 0=未映射 */
static u32 g_lapic_id; /* BSP Local APIC ID (IOAPIC 目的 ID) */
static volatile u32 g_tick_count; /* PIT tick 计数 (路由验证) */
static int g_route_active; /* 1=IOAPIC 路由已接管 */
static __inline__ void apic_mb(void) {
__asm__ volatile("" ::: "memory");
@@ -199,6 +292,24 @@ static int memeq(const char *a, const char *b, u32 n) {
return 1;
}
static u32 str_len(const char *s) {
u32 n = 0;
while (s[n]) n++;
return n;
}
/* 在 NUL 结尾字符串中查找子串 (cmdline 匹配用) */
static int str_contains(const char *hay, const char *needle) {
u32 nlen = str_len(needle);
if (!nlen) return 1;
for (u32 i = 0; hay[i]; i++) {
u32 j = 0;
while (j < nlen && hay[i + j] && hay[i + j] == needle[j]) j++;
if (j == nlen) return 1;
}
return 0;
}
static u8 checksum8(const u8 *data, u32 len) {
u8 sum = 0;
for (u32 i = 0; i < len; i++) sum += data[i];
@@ -219,6 +330,37 @@ static void log_hex(const char *prefix, u64 val) {
g_log->info(buf);
}
static void log_dec(const char *prefix, u64 val) {
char buf[24];
u32 pos = 0;
if (!val) {
buf[pos++] = '0';
} else {
char tmp[20];
u32 n = 0;
while (val) { tmp[n++] = (char)('0' + (val % 10)); val /= 10; }
while (n) buf[pos++] = tmp[--n];
}
buf[pos] = 0;
g_log->info(prefix);
g_log->info(buf);
}
/* ---- I/O 端口 (PIC / PIT) ---- */
static __inline__ void outb(u16 port, u8 value) {
__asm__ volatile ("outb %0, %1" :: "a"(value), "Nd"(port));
}
static __inline__ u8 inb(u16 port) {
u8 value;
__asm__ volatile ("inb %1, %0" : "=a"(value) : "Nd"(port));
return value;
}
static __inline__ void io_wait(void) {
outb(0x80, 0);
}
static void cpuid(u32 leaf, u32 *a, u32 *b, u32 *c, u32 *d) {
__asm__ volatile ("cpuid"
: "=a"(*a), "=b"(*b), "=c"(*c), "=d"(*d)
@@ -232,6 +374,12 @@ static u64 rdmsr(u32 msr) {
return ((u64)hi << 32) | lo;
}
static void wrmsr(u32 msr, u64 val) {
u32 lo = (u32)(val & 0xffffffffu);
u32 hi = (u32)(val >> 32);
__asm__ volatile("wrmsr" :: "a"(lo), "d"(hi), "c"(msr));
}
static int cpu_has_lapic(void) {
u32 a, b, c, d;
cpuid(1, &a, &b, &c, &d);
@@ -241,6 +389,131 @@ static int cpu_has_lapic(void) {
return (d & (1u << 9)) != 0;
}
/* ---- FUCK 配置自解析 (DKM 侧, 不依赖 UTSM ini_parser) ---- *
* UTSM 通过 Limine boot module (cmdline="fuck:config") 预加载 FUCK;
* kernel_api.boot_modules_response 对所有 DKM 驱动可见。
* 此处实现最小分区感知 INI 扫描: 只找 [drivers] 分区下的目标键。
* 缺失文件/缺失键一律回退默认值, 行为与无配置时一致。
*/
/* 解析值 token: 支持 0/1/true/false/on/off/yes/no 及十进制整数 */
static int parse_scalar(const char *line, u64 start, u64 line_len, int defval) {
u64 i = start;
while (i < line_len && (line[i] == ' ' || line[i] == '\t')) i++;
u64 tok_start = i;
while (i < line_len && line[i] != ' ' && line[i] != '\t' &&
line[i] != '#' && line[i] != ';' && line[i] != '\r') i++;
u64 tok_len = i - tok_start;
const char *tok = line + tok_start;
if (!tok_len) return defval;
if (tok_len == 1 && tok[0] == '1') return 1;
if (tok_len == 1 && tok[0] == '0') return 0;
if (tok_len == 4 && memeq(tok, "true", 4)) return 1;
if (tok_len == 5 && memeq(tok, "false", 5)) return 0;
if (tok_len == 2 && memeq(tok, "on", 2)) return 1;
if (tok_len == 3 && memeq(tok, "off", 3)) return 0;
if (tok_len == 3 && memeq(tok, "yes", 3)) return 1;
if (tok_len == 2 && memeq(tok, "no", 2)) return 0;
/* 十进制整数 (允许前导 '-') */
int neg = 0;
u64 j = 0;
if (tok[0] == '-') { neg = 1; j = 1; }
int val = 0;
int any = 0;
for (; j < tok_len; j++) {
if (tok[j] < '0' || tok[j] > '9') return defval;
val = val * 10 + (tok[j] - '0');
any = 1;
}
if (!any) return defval;
return neg ? -val : val;
}
static int fuck_get_int(const char *text, u64 size,
const char *section, const char *key, int defval) {
u64 pos = 0;
int in_section = 0;
u32 slen = str_len(section);
u32 klen = str_len(key);
while (pos < size) {
u64 end = pos;
while (end < size && text[end] != '\n') end++;
u64 line_len = end - pos;
if (line_len > 0 && text[pos + line_len - 1] == '\r') line_len--;
const char *line = text + pos;
u64 i = 0;
while (i < line_len && (line[i] == ' ' || line[i] == '\t')) i++;
if (i < line_len) {
char c = line[i];
if (c == '#' || c == ';') {
/* 注释行, 跳过 */
} else if (c == '[') {
in_section = 0;
if (line_len >= (u64)(i + 1 + slen + 1) &&
memeq(line + i + 1, section, slen) &&
line[i + 1 + slen] == ']') {
in_section = 1;
}
} else if (in_section) {
if (line_len >= (u64)(i + klen) &&
memeq(line + i, key, klen)) {
u64 j = i + klen;
while (j < line_len && (line[j] == ' ' || line[j] == '\t')) j++;
if (j < line_len && line[j] == '=') {
return parse_scalar(line, j + 1, line_len, defval);
}
}
}
}
pos = end + 1;
}
return defval;
}
static void load_fuck_config(const struct dkm_kernel_api *api) {
/* 编译期默认: 与 FUCK 注释承诺一致 (无配置时行为不变) */
g_cfg.lapic_enable = 1;
g_cfg.ioapic_init = 1;
g_cfg.apic_route = 0;
g_cfg.apic_route_legacy = 0;
g_cfg.apic_diag = 0;
const struct limine_module_response_min *resp =
(const struct limine_module_response_min *)api->boot_modules_response;
if (!resp || !resp->modules) {
g_log->warn("[apic] no boot modules; FUCK defaults in effect");
return;
}
for (u64 i = 0; i < resp->module_count; i++) {
struct limine_file_min *f = resp->modules[i];
if (!f || !f->cmdline || !f->address || !f->size) continue;
if (!str_contains(f->cmdline, "fuck:config")) continue;
const char *text = (const char *)f->address;
u64 size = f->size;
g_log->info("[apic] FUCK config module found");
log_hex("[apic] FUCK size=", size);
g_cfg.lapic_enable = fuck_get_int(text, size, "drivers", "lapic_enable", 1);
g_cfg.ioapic_init = fuck_get_int(text, size, "drivers", "ioapic_init", 1);
g_cfg.apic_route = fuck_get_int(text, size, "drivers", "apic_route", 0);
g_cfg.apic_route_legacy = fuck_get_int(text, size, "drivers", "apic_route_legacy", 0);
g_cfg.apic_diag = fuck_get_int(text, size, "drivers", "apic_diag", 0);
log_dec("[apic] cfg lapic_enable=", (u64)g_cfg.lapic_enable);
log_dec("[apic] cfg ioapic_init=", (u64)g_cfg.ioapic_init);
log_dec("[apic] cfg apic_route=", (u64)g_cfg.apic_route);
log_dec("[apic] cfg apic_route_legacy=", (u64)g_cfg.apic_route_legacy);
log_dec("[apic] cfg apic_diag=", (u64)g_cfg.apic_diag);
return;
}
g_log->warn("[apic] FUCK module (fuck:config) not found; defaults in effect");
}
static const acpi_sdt_header *find_table_from_rsdt(const acpi_sdt_header *rsdt, const char sig[4], u32 entry_size) {
if (!rsdt || rsdt->length < sizeof(acpi_sdt_header)) return NULL;
if (checksum8((const u8 *)rsdt, rsdt->length) != 0) return NULL;
@@ -288,6 +561,27 @@ static const acpi_madt *find_madt(const void *rsdp_addr) {
return NULL;
}
/* 查找 HPET 表 (apic_diag 用; 与 find_madt 同构, 仅签名不同) */
static const acpi_hpet *find_hpet(const void *rsdp_addr) {
if (!rsdp_addr) return NULL;
const acpi_rsdp_v2 *rsdp = (const acpi_rsdp_v2 *)rsdp_addr;
if (!memeq(rsdp->signature, "RSD PTR ", 8)) return NULL;
if (checksum8((const u8 *)rsdp, 20) != 0) return NULL;
if (rsdp->revision >= 2 && rsdp->length >= sizeof(acpi_rsdp_v2) && rsdp->xsdt_addr) {
if (checksum8((const u8 *)rsdp, rsdp->length) == 0) {
const acpi_sdt_header *xsdt = (const acpi_sdt_header *)(uintptr_t)rsdp->xsdt_addr;
const acpi_sdt_header *hpet = find_table_from_rsdt(xsdt, "HPET", 8);
if (hpet) return (const acpi_hpet *)hpet;
}
}
if (rsdp->rsdt_addr) {
const acpi_sdt_header *rsdt = (const acpi_sdt_header *)(uintptr_t)(u64)rsdp->rsdt_addr;
const acpi_sdt_header *hpet = find_table_from_rsdt(rsdt, "HPET", 4);
if (hpet) return (const acpi_hpet *)hpet;
}
return NULL;
}
static void parse_madt_entries(const acpi_madt *madt, struct madt_info *info) {
const u8 *base = (const u8 *)madt;
u32 off = (u32)sizeof(acpi_madt);
@@ -376,18 +670,19 @@ static void ioapic_write(volatile u32 *base, u8 reg, u32 val) {
base[IOAPIC_IOWIN / 4] = val;
}
/* IOAPIC 重定向表项 (64-bit, 分两次 32-bit 读写)
/* IOAPIC 重定向表项 (64-bit, 分两次 32-bit 读写, 82093AA 规范布局)
* bits 0-7: interrupt vector (0x20-0xFF)
* bit 8: delivery mode: 0=fixed, 1=lowest, 2=SMI, 4=NMI, 5=INIT, 7=ExtINT
* bit 9: destination mode: 0=physical, 1=logical
* bit 10: delivery status (RO)
* bit 11: polarity: 0=active high, 1=active low
* bit 12: remote IRR (RO, level-triggered only)
* bit 13: trigger mode: 0=edge, 1=level
* bit 14: mask: 0=enabled, 1=masked
* bit 15: reserved
* bits 16-55: reserved
* bits 8-10: delivery mode: 0=fixed, 1=lowest, 2=SMI, 4=NMI, 5=INIT, 7=ExtINT
* bit 11: destination mode: 0=physical, 1=logical
* bit 12: delivery status (RO)
* bit 13: polarity: 0=active high, 1=active low
* bit 14: remote IRR (RO, level-triggered only)
* bit 15: trigger mode: 0=edge, 1=level
* bit 16: mask: 0=enabled, 1=masked
* bits 17-55: reserved
* bits 56-63: destination APIC ID (physical mode) or MDA (logical mode)
* 注: 历史版本曾将 polarity/trigger/mask 错置于 bit 11/13/14,
* 导致 mask 写入只读 remote IRR 位而从未生效 —— 勿回退。
*/
static u64 ioapic_read_redir(volatile u32 *base, u8 entry) {
u32 lo = ioapic_read(base, (u8)(0x10 + entry * 2));
@@ -400,13 +695,96 @@ static void ioapic_write_redir(volatile u32 *base, u8 entry, u64 val) {
ioapic_write(base, (u8)(0x10 + entry * 2 + 1), (u32)(val >> 32));
}
/* ---- B7: GSI 路由辅助 ---- */
/* 合成重定向表项: vector + ISO 极性/触发 + 物理目的 APIC ID + mask 位 */
static u64 redir_compose(u32 vector, u16 iso_flags, u32 dest_apic_id, int masked) {
u64 entry = (u64)(vector & 0xFFu);
/* delivery mode = fixed (0), destination mode = physical (0) */
/* 极性: bit 13. ISA 默认 active-high (0).
* ISO flags bit 0-1: 0=conforms, 1=active-high, 2=reserved, 3=active-low */
if ((iso_flags & 0x03u) == 0x03u) entry |= (1ULL << 13);
/* 触发: bit 15. ISA 默认 edge (0).
* ISO flags bit 2-3: 0=conforms, 1=edge, 2=reserved, 3=level */
if ((iso_flags & 0x0Cu) == 0x0Cu) entry |= (1ULL << 15);
entry |= ((u64)dest_apic_id << 56);
if (masked) entry |= (1ULL << 16);
return entry;
}
/* 找覆盖指定 GSI 的 IOAPIC (多 IOAPIC 按 GSI 范围划分) */
static struct ioapic_info *ioapic_for_gsi(struct madt_info *info, u32 gsi) {
for (u32 i = 0; i < info->ioapic_count; i++) {
struct ioapic_info *io = &info->ioapics[i];
if (gsi >= io->gsi_base && gsi < io->gsi_base + io->gsi_count) return io;
}
return NULL;
}
/* ISA IRQ → GSI 解析: 优先 MADT ISO override, 缺省恒等 (QEMU ISA 惯例) */
static u32 isa_irq_to_gsi(u8 isa_irq, u16 *flags_out,
const struct iso_info *isos, u32 iso_count) {
for (u32 i = 0; i < iso_count; i++) {
if (isos[i].bus_source == isa_irq) {
*flags_out = isos[i].flags;
return isos[i].gsi;
}
}
*flags_out = 0;
return (u32)isa_irq;
}
/* 编程单个 GSI 的重定向项 (unmask=1 放开投递, 0 保持屏蔽) */
static int ioapic_program_gsi(struct madt_info *info, u32 gsi,
u32 vector, u16 iso_flags, int unmask) {
struct ioapic_info *io = ioapic_for_gsi(info, gsi);
if (!io || !io->mmio) return -1;
u8 pin = (u8)(gsi - io->gsi_base);
ioapic_write_redir(io->mmio, pin,
redir_compose(vector, iso_flags, g_lapic_id, !unmask));
return 0;
}
/* 全 IOAPIC 全条目屏蔽 (降级路径) — 保留 vector 信息, 仅置 mask 位 */
static void ioapic_mask_all(struct madt_info *info) {
for (u32 i = 0; i < info->ioapic_count; i++) {
struct ioapic_info *io = &info->ioapics[i];
if (!io->mmio) continue;
for (u32 pin = 0; pin < io->gsi_count && pin < 64; pin++) {
u64 entry = ioapic_read_redir(io->mmio, (u8)pin);
entry |= (1ULL << 16);
ioapic_write_redir(io->mmio, (u8)pin, entry);
}
}
}
/* 重定向表回读 dump (验证证据) */
static void ioapic_dump_table(struct madt_info *info) {
for (u32 i = 0; i < info->ioapic_count; i++) {
struct ioapic_info *io = &info->ioapics[i];
if (!io->mmio) continue;
g_log->info("[apic] IOREDTBL readback:");
for (u32 pin = 0; pin < io->gsi_count && pin < 24; pin++) {
u64 entry = ioapic_read_redir(io->mmio, (u8)pin);
u32 vec = (u32)(entry & 0xFFu);
u32 masked = (u32)((entry >> 16) & 1u);
log_dec("[apic] pin=", pin);
log_hex("[apic] vec=", vec);
log_dec("[apic] mask=", masked);
}
}
}
/* ---- IOAPIC 初始化 ---- *
* 逐条目编程重定向表: ISA IRQ→vector 映射 + mask all。
* 真机关键: 处理 MADT ISO (IRQ0→GSI2 是最常见的 override)。
* PIC 兼容模式: vector = 0x20 + IRQ (与 PIC remap 布局一致)。
*
* **QEMU 无法测试但真机必需的代码路径**:
* 1. ISO 处理: 真机几乎总有 IRQ0→GSI2 override, QEMU 默认无 override
* 1. ISO 处理: 真机几乎总有 IRQ0→GSI2 override, QEMU 默认有 1 条
* 2. 极性标志: 真机 ISA IRQ0 (timer) 必须设 active-high,
* 但 ISO 可能指定 active-low (PCIe SCI 等)
* 3. 多 IOAPIC: 真机可能有 2+ IOAPIC, 每个 GSI 范围不同
@@ -480,29 +858,12 @@ static void ioapic_init_one(struct ioapic_info *io, u64 hhdm_offset,
u32 vec = gsi_to_vector[gsi];
u16 flags = gsi_flags[gsi];
entry = (u64)vec; /* vector */
/* delivery mode = fixed (0) */
/* destination mode = physical (0) */
/* 极性: bit 11. ISA 默认 active-high (0).
* ISO flags bit 1: 0=conforms (active-high for ISA), 1=active-low */
if (flags & 0x03) {
if (flags & 0x02) entry |= (1ULL << 11); /* active low */
}
/* 触发模式: bit 13. ISA 默认 edge (0).
* ISO flags bit 3: 0=conforms (edge for ISA), 1=level */
if (flags & 0x08) {
if (flags & 0x04) entry |= (1ULL << 13); /* level triggered */
}
/* destination: BSP APIC ID (读自 LAPIC) */
/* 暂时硬编码 destination = 0 (BSP), 后续从 LAPIC ID 寄存器获取 */
entry |= (0ULL << 56); /* destination APIC ID = 0 */
/* vector + ISO 极性/触发 + 真实 BSP LAPIC ID, 保持 mask */
entry = redir_compose(vec, flags, g_lapic_id, 1);
} else {
entry = (1ULL << 16); /* 无映射条目: 纯 mask (bit16, vector=0 无害) */
}
entry |= (1ULL << 14); /* mask = 1, 所有条目默认屏蔽 */
ioapic_write_redir(io->mmio, (u8)pin, entry);
}
@@ -519,14 +880,8 @@ static void ioapic_init_one(struct ioapic_info *io, u64 hhdm_offset,
* 但实机 BIOS 可能留 LAPIC 为 software-disable 状态。
*/
static void wrmsr(u32 msr, u64 val) {
u32 lo = (u32)(val & 0xffffffffu);
u32 hi = (u32)(val >> 32);
__asm__ volatile("wrmsr" :: "a"(lo), "d"(hi), "c"(msr));
}
static void lapic_enable(u64 hhdm_offset, u64 lapic_phys) {
volatile u32 *lapic = (volatile u32 *)(uintptr_t)(hhdm_offset + lapic_phys);
static void lapic_enable(void) {
volatile u32 *lapic = g_lapic;
/* 确保 IA32_APIC_BASE 的 global enable 和 BSP 位已设置 */
u64 apic_base = rdmsr(0x1B);
@@ -559,6 +914,532 @@ static void lapic_enable(u64 hhdm_offset, u64 lapic_phys) {
log_hex("[apic] LAPIC version=", lapic[0x30 / 4]);
}
/* LAPIC EOI — 写 0 到 EOI 寄存器 (offset 0xB0)。
* B7 阶段2: 本函数作为 EOI 钩子经 kernel_api.register_apic_eoi 注册给 UTSM,
* idt_handler 在两个 IRQ 分支的分发末尾调用 (钩子签名 void(*)(void) 匹配)。
* 调用点: IRQ 上下文, 单核 BSP interrupt gate (IF=0), 无嵌套, 无需锁。 */
static __inline__ void lapic_eoi(void) {
if (g_lapic) {
g_lapic[0xB0 / 4] = 0;
apic_mb();
}
}
/* ---- B7 diag: HPET 绝对时基 (tick 2x 定论实验) ---- *
* 选型理由: TSC 校准 (PIT mode0 回绕法) 与被测对象 (PIT mode3 投递频率)
* 共享 PIT 硬件, 循环论证无法定论 2x 根因。HPET 主计数器是独立时基
* (QEMU q35: 100MHz, period=10000fs), 32 位回绕周期 ~42.9s, 对 ms 级
* 窗口充裕。只设 GEN_CONF bit0 (overall enable); 绝不置 bit1
* (legacy replacement route 会抢 PIT IRQ0/RTC IRQ8 路由, 破坏被测链路)。
*/
static volatile u32 *g_hpet; /* HPET MMIO 基址 (HHDM), 0=不可用 */
static u32 g_hpet_period_fs; /* 主计数器周期 (飞秒), GCAP_ID[63:32] */
#define HPET_GCAP_ID 0x00
#define HPET_GEN_CONF 0x10
#define HPET_MAIN_CNT 0xF0
static u64 hpet_read64(u32 off) {
volatile u32 *p = (volatile u32 *)((volatile u8 *)g_hpet + off);
u32 lo = p[0];
u32 hi = p[1];
return ((u64)hi << 32) | lo;
}
static void hpet_write64(u32 off, u64 v) {
volatile u32 *p = (volatile u32 *)((volatile u8 *)g_hpet + off);
p[0] = (u32)(v & 0xffffffffu);
p[1] = (u32)(v >> 32);
}
static __inline__ u32 hpet_main32(void) {
return *(volatile u32 *)((volatile u8 *)g_hpet + HPET_MAIN_CNT);
}
/* 初始化 HPET 时基; 成功返回 0 且 g_hpet/g_hpet_period_fs 有效 */
static int hpet_diag_init(const struct dkm_kernel_api *api) {
const acpi_hpet *hpet = find_hpet(api->rsdp_address);
if (!hpet) {
g_log->warn("[apic] diag: HPET table not found");
return -1;
}
if (hpet->addr_space_id != 0 || !hpet->base_addr) {
g_log->warn("[apic] diag: HPET not memory-mapped");
return -1;
}
g_hpet = (volatile u32 *)(uintptr_t)(api->hhdm_offset + hpet->base_addr);
u64 gcap = hpet_read64(HPET_GCAP_ID);
g_hpet_period_fs = (u32)(gcap >> 32);
if (!g_hpet_period_fs || g_hpet_period_fs > 100000000u) {
g_log->warn("[apic] diag: HPET period implausible");
g_hpet = 0;
return -1;
}
log_hex("[apic] diag: HPET base=", hpet->base_addr);
log_dec("[apic] diag: HPET period(fs)=", g_hpet_period_fs);
/* overall enable (bit0); legacy replacement route (bit1) 保持 0 */
u64 conf = hpet_read64(HPET_GEN_CONF);
if (!(conf & 1u)) {
hpet_write64(HPET_GEN_CONF, conf | 1u);
apic_mb();
}
g_log->info("[apic] diag: HPET main counter enabled");
return 0;
}
/* HPET 主计数器差值 → 微秒 (全程 u64 整数, -msoft-float 环境无浮点) */
static u64 hpet_delta_us(u32 h0, u32 h1) {
/* 无符号回绕安全: u32 减法自动回绕 (42.9s 周期, 窗口 << 此值) */
u64 ticks = (u64)(u32)(h1 - h0);
/* ticks * period_fs / 1e9 = us; period_fs 典型 10000, 500ms 窗口
* ticks≈5e7, 乘积 5e11 << 2^64, 溢出安全 */
return (ticks * (u64)g_hpet_period_fs) / 1000000000ull;
}
/* ---- B7 diag: PS/2 鼠标流 (IRQ12 持续投递验证) ---- *
* UTSM 阶段 IRQ12 无属主 (mouseInit 是 DSK 阶段模块), diag handler 独占;
* DSK mouseInit 之后经 irq_register(12) 覆盖本 handler 并完整复位鼠标,
* 无状态冲突。所有单步带超时, 失败只记日志不阻塞。 */
static volatile u32 g_mouse_pkt_count;
static int ps2_wait_ibf(void) {
u32 t = 100000;
while (t--) { if (!(inb(0x64) & 0x02)) return 0; }
return -1;
}
static int ps2_wait_obf(void) {
u32 t = 100000;
while (t--) { if (inb(0x64) & 0x01) return 0; }
return -1;
}
static void ps2_drain(void) {
while (inb(0x64) & 0x01) { (void)inb(0x60); }
}
/* 经 0xD4 通道写鼠标命令并等 ACK(0xFA); 失败返回 -1 */
static int ps2_aux_cmd(u8 cmd) {
if (ps2_wait_ibf() != 0) return -1;
outb(0x64, 0xD4);
if (ps2_wait_ibf() != 0) return -1;
outb(0x60, cmd);
if (ps2_wait_obf() != 0) return -1;
return inb(0x60) == 0xFA ? 0 : -1;
}
/* IRQ12 诊断 handler: 只读数据计数, EOI 交给 idt_handler 分发路径
* (PIC 模式: 8259 EOI; route 模式: 钩子 LAPIC EOI + 无害 8259 EOI)。
* 返回 0 — 不自负 EOI, 与 ps2kbd/e1000 handler 同一约定。 */
static int apic_mouse_diag_handler(u8 vector) {
(void)vector;
u8 st = inb(0x64);
if ((st & 0x01) && (st & 0x20)) { /* OBF 且 AUX 位置位 = 鼠标数据 */
u8 data = inb(0x60); /* 必须读, 否则 OBF 保持阻塞后续包 */
g_mouse_pkt_count++;
if (g_mouse_pkt_count <= 6) {
g_log->info("[apic] diag: mouse packet byte");
log_hex("[apic] diag: data=", data);
}
}
return 0;
}
/* IRQ1 诊断 handler (键盘流计数): ps2kbd demo 窗口只有 ~10ms 数量级,
* 自动化无法抓住; diag 在 stage0 自建 3s 观察窗验证 IRQ1 持续投递。
* stage3 ps2kbd 注册时覆盖本 handler, 无冲突。 */
static volatile u32 g_kbd_pkt_count;
static int apic_kbd_diag_handler(u8 vector) {
(void)vector;
u8 st = inb(0x64);
if ((st & 0x01) && !(st & 0x20)) { /* OBF 且 AUX 位清零 = 键盘数据 */
u8 data = inb(0x60); /* 读走扫描码 (窗内按键, 无消费者) */
g_kbd_pkt_count++;
if (g_kbd_pkt_count <= 6) {
g_log->info("[apic] diag: kbd scancode byte");
log_hex("[apic] diag: sc=", data);
}
}
return 0;
}
/* AUX 端口初始化: 启用 AUX 时钟 + 采样率 100 + 数据报告, 最后才开 AUX IRQ。
* 顺序要点: AUX IRQ (cfg bit1) 必须在全部命令/ACK 交互完成后才使能 —
* 否则 ACK 字节 (0xFA) 到达即触发 IRQ12 被 diag handler 读走, 轮询侧
* 永远等不到 ACK (实测 F3 nack 即此竞态: handler 日志 data=0xfa)。
* 单步失败只记日志继续 (与 mouseInit 同一安全准则)。 */
static void ps2_aux_enable_stream(void) {
ps2_drain();
if (ps2_wait_ibf() != 0) { g_log->warn("[apic] diag: PS2 ibf timeout"); return; }
outb(0x64, 0x20); /* 读配置字节 */
if (ps2_wait_obf() != 0) { g_log->warn("[apic] diag: PS2 cfg read timeout"); return; }
u8 cfg = inb(0x60);
cfg &= (u8)~0x02; /* bit1=0: AUX IRQ 先关闭 (轮询期) */
cfg &= (u8)~0x20; /* bit5=0: AUX clock enable */
cfg |= 0x01; /* bit0=1: KBD IRQ 保持使能 (键盘流用) */
if (ps2_wait_ibf() != 0) return;
outb(0x64, 0x60); /* 写配置字节 */
if (ps2_wait_ibf() != 0) return;
outb(0x60, cfg);
log_hex("[apic] diag: PS2 cfg byte=", cfg);
if (ps2_wait_ibf() != 0) return;
outb(0x64, 0xA8); /* 启用 AUX 端口 */
if (ps2_aux_cmd(0xF3) != 0) { g_log->warn("[apic] diag: mouse F3 nack"); }
else if (ps2_aux_cmd(100) != 0) { g_log->warn("[apic] diag: mouse rate nack"); }
if (ps2_aux_cmd(0xF4) != 0) { g_log->warn("[apic] diag: mouse F4 nack"); return; }
g_log->info("[apic] diag: PS/2 mouse data reporting enabled");
ps2_drain(); /* 清 F4 ACK 后可能挂起的首个流字节 */
/* 全部交互完成, 现在才开 AUX IRQ — 之后鼠标字节全走 IRQ12 handler */
if (ps2_wait_ibf() != 0) return;
outb(0x64, 0x20);
if (ps2_wait_obf() != 0) return;
cfg = inb(0x60);
cfg |= 0x02; /* bit1=1: AUX IRQ enable */
if (ps2_wait_ibf() != 0) return;
outb(0x64, 0x60);
if (ps2_wait_ibf() != 0) return;
outb(0x60, cfg);
log_hex("[apic] diag: PS2 cfg byte(final)=", cfg);
}
/* ---- B7 阶段2: PIT tick 路由验证 ---- *
* vector 0x20 落在 idt_handler 的 PIC 分支 (vector 32-47) — 与键盘 0x21/
* 鼠标 0x2C/网卡 0x2B 同一代码路径。分发顺序: handler(本函数) → 钩子
* LAPIC EOI → 8259 EOI (PIC 全屏蔽下为无害 no-op)。
* 本 handler 返回 0 不自负 EOI: 由 UTSM 钩子写 LAPIC EOI 寄存器清除
* ISR 位; 重复投递 (计数>=2) 即证明"legacy 向量分支 + 钩子 EOI"可持续,
* 这正是键盘/鼠标/网卡将要走的路径 (阶段1 的 0xE0+自EOI 无法证明此路径)。
* handler 是普通 C 函数 (SysV ABI), isr stub 已完成现场保存/iret。
*/
static int apic_tick_handler(u8 vector) {
(void)vector;
g_tick_count++;
if (g_tick_count <= 8) {
g_log->info("[apic] ioapic tick (PIT GSI -> LAPIC -> IDT vec 0x20, hook EOI)");
}
return 0;
}
static u64 rdtsc(void) {
u32 lo, hi;
__asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
return ((u64)hi << 32) | lo;
}
static u64 g_tsc_per_ms;
/* PIT ch0 mode 0 单发 (~10ms, 11932 counts) 校准 TSC。
* 必须在把 PIT 改为 mode 3 周期模式之前调用。 */
static void tsc_calibrate(void) {
outb(0x43, 0x30); /* ch0, lo+hi, mode 0, binary */
outb(0x40, 0x9C); /* 11932 low = ~10ms */
outb(0x40, 0x2E); /* 11932 high */
u64 tsc_start = rdtsc();
u16 prev = 0;
u64 loops = 0;
for (;;) {
outb(0x43, 0x00);
u16 cur = (u16)inb(0x40) | ((u16)inb(0x40) << 8);
if (cur > prev && loops > 10) break; /* 回绕 = 10ms 到 */
prev = cur;
loops++;
}
u64 tsc_end = rdtsc();
g_tsc_per_ms = (tsc_end - tsc_start) / 10;
}
static void tsc_delay_ms(u32 ms) {
if (!g_tsc_per_ms) {
for (volatile u32 i = 0; i < 100000 * ms; i++) __asm__ volatile("pause");
return;
}
u64 target = g_tsc_per_ms * ms;
u64 start = rdtsc();
while (rdtsc() - start < target) __asm__ volatile("pause");
}
/* ---- B7 阶段2: 路由切换主流程 ---- *
* 成功: PIC IMR 全 1, IOAPIC 拥有路由, EOI 钩子保持注册, tick 验证后
* tick 线重新 mask (保留路由表内容), legacy 线按 apic_route_legacy。
* 失败: IOAPIC 全 mask + 恢复原 PIC IMR + 注销 tick handler + 注销 EOI
* 钩子, 与切换前状态完全一致 (现状不回归)。
*/
static void apic_route_switch(const struct dkm_kernel_api *api,
struct madt_info *info) {
g_log->info("[apic] apic_route=1: attempting IOAPIC routing takeover");
if (!g_lapic) {
g_log->warn("[apic] route abort: LAPIC MMIO unavailable; PIC retained");
return;
}
if (info->ioapic_count == 0) {
g_log->warn("[apic] route abort: no IOAPIC; PIC retained");
return;
}
if (!api->irq_register) {
g_log->warn("[apic] route abort: irq_register missing; PIC retained");
return;
}
/* 0) B7 阶段2 前提: EOI 钩子接口必须存在 (旧内核无此字段 → 判空中止)。
* 无钩子时 legacy 向量 (0x20-0x2F) 在 IOAPIC 路由下只能 one-shot,
* 路由接管不成立 — 宁可保留 PIC 也不进入半残状态。 */
if (!api->register_apic_eoi) {
g_log->warn("[apic] route abort: register_apic_eoi missing (old kernel); PIC retained");
return;
}
api->register_apic_eoi(lapic_eoi);
g_log->info("[apic] LAPIC EOI hook registered to UTSM");
/* 1) 保存 PIC IMR (降级恢复用) */
u8 imr1 = inb(0x21);
u8 imr2 = inb(0xA1);
log_hex("[apic] PIC IMR saved master=", imr1);
log_hex("[apic] PIC IMR saved slave=", imr2);
/* 2) TSC 校准 (PIT 尚在 firmware/driver 遗留状态, 先校准再改模式)。
* diag: HPET 绝对时基对拍本窗口 — 校准窗口标称 10ms, HPET 实测
* 若 ~20000us 则 PIT mode0 实际跑了 20ms (TSC 测半), ~10000us
* 则校准正确, tick 2x 根因另在他处。 */
int hpet_ok = 0;
if (g_cfg.apic_diag) {
hpet_ok = (hpet_diag_init(api) == 0);
}
u32 hc0 = 0;
if (hpet_ok) hc0 = hpet_main32();
tsc_calibrate();
if (hpet_ok) {
u32 hc1 = hpet_main32();
u64 calib_us = hpet_delta_us(hc0, hc1);
log_dec("[apic] diag: calib window(us)=", calib_us);
g_log->info("[apic] diag: (nominal 10000us; ~20000 => TSC half-measured)");
}
log_hex("[apic] tsc_per_ms=", g_tsc_per_ms);
/* 3) 注册 tick handler @ vector 0x20 — PIC 分支 (legacy 向量路径),
* handler 返回 0, LAPIC EOI 由 UTSM 钩子完成 */
if (api->irq_register(0x20, (void *)(uintptr_t)apic_tick_handler) != 0) {
g_log->warn("[apic] route abort: irq_register(0x20) failed");
api->register_apic_eoi(NULL);
return;
}
g_log->info("[apic] tick handler registered at vector 0x20 (PIC branch, hook EOI)");
/* 4) PIT tick GSI 解析: MADT ISO source 0 (QEMU q35: GSI2), 缺省恒等 */
u16 tick_flags = 0;
u32 tick_gsi = isa_irq_to_gsi(0, &tick_flags, info->isos, info->iso_count);
log_hex("[apic] tick GSI=", tick_gsi);
log_hex("[apic] tick GSI flags=", tick_flags);
/* 5) 编程 tick 重定向项 (vector 0x20, 放开) */
if (ioapic_program_gsi(info, tick_gsi, 0x20, tick_flags, 1) != 0) {
g_log->warn("[apic] route abort: tick GSI not covered by any IOAPIC");
api->irq_register(0x20, (void *)0);
api->register_apic_eoi(NULL);
return;
}
g_log->info("[apic] tick redirection unmasked (vec 0x20)");
/* 6) legacy 线 (apic_route_legacy): EOI 钩子已就位, 持续投递成立。
* ISA 线 (IRQ1/12) 按 ISO, 缺省 edge/active-high;
* PCI 线 (IRQ11 e1000) 无 ISO 覆盖时强制 level/active-low
* (PCI INTx 规范; flags=0xF = pol bit0-1=3 active-low + trig bit2-3=3
* level)。e1000 handler 读 ICR 清源 → 电平 deassert → 钩子 EOI。 */
if (g_cfg.apic_route_legacy) {
u16 f1 = 0, f12 = 0, f11 = 0;
u32 gsi1 = isa_irq_to_gsi(1, &f1, info->isos, info->iso_count);
u32 gsi12 = isa_irq_to_gsi(12, &f12, info->isos, info->iso_count);
u32 gsi11 = isa_irq_to_gsi(11, &f11, info->isos, info->iso_count);
if (!f11) f11 = 0x0F;
ioapic_program_gsi(info, gsi1, 0x21, f1, 1); /* 键盘 */
ioapic_program_gsi(info, gsi12, 0x2C, f12, 1); /* 鼠标 */
ioapic_program_gsi(info, gsi11, 0x2B, f11, 1); /* e1000 (QEMU 默认 IRQ11) */
g_log->info("[apic] legacy IRQ1/12/11 unmasked (sustained delivery via EOI hook)");
log_hex("[apic] kbd gsi=", gsi1);
log_hex("[apic] mouse gsi=", gsi12);
log_hex("[apic] e1000 gsi=", gsi11);
log_hex("[apic] e1000 flags=", f11);
}
/* 7) LAPIC TPR=0 全放行 */
g_lapic[0x80 / 4] = 0;
apic_mb();
log_hex("[apic] LAPIC TPR=", g_lapic[0x80 / 4]);
/* 8) PIC 全屏蔽 (先 slave 后 master, 避免 cascade 窗口; QEMU 无功能 IMCR) */
outb(0xA1, 0xFF);
io_wait();
outb(0x21, 0xFF);
io_wait();
g_log->info("[apic] PIC fully masked; IOAPIC owns IRQ routing");
/* 9) PIT ch0 mode 3 周期 ~100Hz (divisor 11932), 产生真实 tick 源 */
outb(0x43, 0x36);
outb(0x40, 0x9C);
outb(0x40, 0x2E);
g_log->info("[apic] PIT ch0 mode3 100Hz started");
/* 10) 500ms 验证窗口 (+ diag: HPET 实测窗口与 tick 频率定论) */
u32 hv0 = 0;
if (hpet_ok) hv0 = hpet_main32();
u32 before = g_tick_count;
tsc_delay_ms(500);
u32 ticks = g_tick_count - before;
if (hpet_ok) {
u32 hv1 = hpet_main32();
u64 win_us = hpet_delta_us(hv0, hv1);
log_dec("[apic] diag: verify window(us)=", win_us);
if (win_us) {
u64 hz_x10 = (u64)ticks * 10000000ull / win_us;
log_dec("[apic] diag: tick rate(Hz x10)=", hz_x10);
}
/* tick 2x 定论分支:
* win≈500ms 且 ticks≈100 → PIT mode3 在 IOAPIC edge 下双触发 (200Hz 真实)
* win≈1000ms 且 ticks≈100 → TSC 校准窗口翻倍 (tick 频率真实 100Hz)
* win≈500ms 且 ticks≈50 → 一切正常 */
if (win_us > 750000) {
g_log->info("[apic] diag: TICK2X VERDICT: TSC calib window doubled (PIT mode0 ran ~20ms); tick rate nominal");
} else if (ticks > 75) {
g_log->info("[apic] diag: TICK2X VERDICT: PIT mode3 double-clocked on IOAPIC edge (~200Hz real)");
} else {
g_log->info("[apic] diag: TICK2X VERDICT: nominal (~100Hz, no 2x)");
}
}
log_dec("[apic] route verify ticks=", ticks);
if (ticks >= 2) {
/* 成功: 重复投递证明钩子 EOI 正确清除 ISR 位, 路由链路完整 */
g_log->info("[apic] ROUTE VERIFY OK — IOAPIC->LAPIC->IDT(vec 0x20)->handler->hook EOI chain proven");
/* diag: 第二窗口持续投递证据 (排除窗口内偶发两次的弱证明) */
if (g_cfg.apic_diag) {
u32 before2 = g_tick_count;
tsc_delay_ms(500);
u32 ticks2 = g_tick_count - before2;
log_dec("[apic] diag: sustain window ticks=", ticks2);
if (ticks2 >= 2) {
g_log->info("[apic] diag: SUSTAINED delivery confirmed (2nd window)");
}
}
/* tick 线重新 mask (保留路由表, vector 保持 0x20), 避免后台周期中断;
* PIT 恢复 mode 3 @ 18.2Hz (BIOS 惯例值, 已被 mask 不会投递)。
* EOI 钩子保持注册 — legacy 线的持续投递依赖它。 */
ioapic_program_gsi(info, tick_gsi, 0x20, tick_flags, 0);
outb(0x43, 0x36);
outb(0x40, 0x00);
outb(0x40, 0x00);
g_route_active = 1;
g_log->info("[apic] APIC ROUTING ACTIVE (PIC masked; EOI hook live; tick line re-masked)");
} else {
/* 失败: 完整降级回 PIC (含注销 EOI 钩子) */
g_log->error("[apic] ROUTE VERIFY FAILED — degrading to PIC routing");
ioapic_mask_all(info);
api->irq_register(0x20, (void *)0);
api->register_apic_eoi(NULL);
outb(0x43, 0x36);
outb(0x40, 0x00);
outb(0x40, 0x00);
outb(0x21, imr1);
io_wait();
outb(0xA1, imr2);
io_wait();
g_log->info("[apic] PIC routing restored (IMR restored, IOAPIC masked, hook cleared)");
}
}
/* ---- B7 diag: IRQ1/IRQ12 键鼠流观察窗 ---- *
* 在 route switch 之后调用 (route 成功走 IOAPIC; 否则走 PIC IMR)。
* 打锚点日志供测试脚本/人工在窗口内用 QEMU monitor sendkey/mouse_move
* 注入输入; 窗口结束分别报告收包计数。计数 >1 即证明该线在当前路由
* 模式下可持续投递 (一次按键=make+break 两包, 一次移动=多包)。 */
static void apic_diag_mouse_window(const struct dkm_kernel_api *api,
struct madt_info *info) {
g_log->info("[apic] diag: IRQ1/IRQ12 input stream check begin");
/* 确保 TSC 延时可用 (route=0 时未校准: 现场校准并恢复 PIT 惯例状态) */
if (!g_tsc_per_ms) {
tsc_calibrate();
outb(0x43, 0x36);
outb(0x40, 0x00);
outb(0x40, 0x00);
}
if (!api->irq_register) return;
if (g_route_active) {
/* IOAPIC 路由: 确保 IRQ12/IRQ1 重定向项放开 (legacy=1 时幂等重编) */
u16 f12 = 0, f1 = 0;
u32 gsi12 = isa_irq_to_gsi(12, &f12, info->isos, info->iso_count);
u32 gsi1 = isa_irq_to_gsi(1, &f1, info->isos, info->iso_count);
ioapic_program_gsi(info, gsi12, 0x2C, f12, 1);
ioapic_program_gsi(info, gsi1, 0x21, f1, 1);
g_log->info("[apic] diag: IRQ12+IRQ1 via IOAPIC redirection");
} else {
/* PIC 路由: 解 slave IMR bit4 (IRQ12); master IMR bit1 (IRQ1)
* UTSM pic_remap 已留 0xFD (IRQ1 enable), 幂等确认。
* diag 属实验开关, 显式改变 PIC 状态并打日志; UTSM PIC 分支
* 会做 8259 EOI, diag handler 返回 0 即可。 */
outb(0xA1, (u8)(inb(0xA1) & (u8)~0x10u));
outb(0x21, (u8)(inb(0x21) & (u8)~0x02u));
g_log->info("[apic] diag: IRQ12+IRQ1 via PIC (IMR bits cleared)");
}
/* 控制器交互必须全轮询完成: 期间到达的 IRQ (KBD bit0 一直开着)
* 因 handler 未注册只会被 EOI 丢弃, 不会偷走命令响应字节。
* (实测教训: 先注册 handler 时, 0x20 读 cfg 的响应被 kbd handler
* 当扫描码读走, 轮询侧 cfg read timeout) */
ps2_aux_enable_stream();
/* 交互全部完成, 现在才注册诊断 handler */
if (api->irq_register(0x2C, (void *)(uintptr_t)apic_mouse_diag_handler) != 0) {
g_log->warn("[apic] diag: irq_register(0x2C) failed");
return;
}
if (api->irq_register(0x21, (void *)(uintptr_t)apic_kbd_diag_handler) != 0) {
g_log->warn("[apic] diag: irq_register(0x21) failed");
return;
}
/* 6000ms 窗口: 注入方需经 "串口日志落盘(块缓冲) → 测试脚本轮询" 的
* 可见性延迟 (~1s 量级), 3s 窗口实测会被注入延迟吃光 */
g_log->info("[apic] diag: INPUT OBSERVE WINDOW 6000ms — sendkey/mouse_move now");
u32 m_before = g_mouse_pkt_count;
u32 k_before = g_kbd_pkt_count;
tsc_delay_ms(6000);
u32 mpkts = g_mouse_pkt_count - m_before;
u32 kpkts = g_kbd_pkt_count - k_before;
log_dec("[apic] diag: mouse packets observed=", mpkts);
log_dec("[apic] diag: kbd packets observed=", kpkts);
if (mpkts > 1) {
g_log->info("[apic] diag: IRQ12 SUSTAINED delivery confirmed");
} else if (mpkts == 1) {
g_log->warn("[apic] diag: IRQ12 single packet only (check EOI path)");
} else {
g_log->info("[apic] diag: no mouse packets (inject via monitor mouse_move)");
}
if (kpkts > 1) {
g_log->info("[apic] diag: IRQ1 SUSTAINED delivery confirmed");
} else if (kpkts == 1) {
g_log->warn("[apic] diag: IRQ1 single packet only (check EOI path)");
} else {
g_log->info("[apic] diag: no kbd packets (inject via monitor sendkey)");
}
/* handler 保持注册: stage3 ps2kbd 经 irq_register(1) 覆盖键盘 handler,
* DSK mouseInit 经 irq_register(12) 覆盖鼠标 handler, 均无冲突;
* 重定向项/IMR 保持现状 (ps2kbd/mouseInit 有完整初始化序列)。 */
}
__attribute__((visibility("default")))
int driver_init(const struct dkm_kernel_api *api,
struct dkm_driver_handle *handle) {
@@ -575,6 +1456,9 @@ int driver_init(const struct dkm_kernel_api *api,
}
g_log->info("[apic] kernel IRQ backend present: PIC");
/* B7: FUCK 配置自解析 (lapic_enable/ioapic_init/apic_route/apic_route_legacy/apic_diag) */
load_fuck_config(api);
if (!cpu_has_lapic()) {
g_log->warn("[apic] CPUID local APIC not present; PIC fallback active");
g_log->info("[apic] driver ready");
@@ -612,29 +1496,60 @@ int driver_init(const struct dkm_kernel_api *api,
}
if (api->hhdm_offset && info.lapic_phys) {
volatile u32 *lapic = (volatile u32 *)(uintptr_t)(api->hhdm_offset + info.lapic_phys);
log_hex("[apic] LAPIC ID reg=", lapic[0x20 / 4]);
log_hex("[apic] LAPIC version reg=", lapic[0x30 / 4]);
log_hex("[apic] LAPIC SVR reg=", lapic[0xF0 / 4]);
g_lapic = (volatile u32 *)(uintptr_t)(api->hhdm_offset + info.lapic_phys);
g_lapic_id = g_lapic[0x20 / 4] >> 24;
log_hex("[apic] LAPIC ID reg=", g_lapic[0x20 / 4]);
log_hex("[apic] LAPIC version reg=", g_lapic[0x30 / 4]);
log_hex("[apic] LAPIC SVR reg=", g_lapic[0xF0 / 4]);
log_hex("[apic] LAPIC TPR reg=", g_lapic[0x80 / 4]);
log_hex("[apic] BSP LAPIC ID=", g_lapic_id);
} else {
g_log->warn("[apic] HHDM unavailable; skip LAPIC MMIO read");
g_lapic = (volatile u32 *)0;
}
/* ---- LAPIC 使能(真机必需,QEMU 也可安全执行) ---- */
if (api->hhdm_offset && info.lapic_phys) {
lapic_enable(api->hhdm_offset, info.lapic_phys);
if (g_lapic && g_cfg.lapic_enable) {
lapic_enable();
} else if (!g_cfg.lapic_enable) {
g_log->info("[apic] lapic_enable=0: skip LAPIC software enable");
}
/* ---- IOAPIC 初始化(真机必需,QEMU 也可安全执行) ---- */
for (u32 i = 0; i < info.ioapic_count; i++) {
g_log->info("[apic] initializing IOAPIC");
log_hex("[apic] index=", i);
ioapic_init_one(&info.ioapics[i], api->hhdm_offset,
info.isos, info.iso_count,
api->mm_map_mmio);
if (g_cfg.ioapic_init) {
for (u32 i = 0; i < info.ioapic_count; i++) {
g_log->info("[apic] initializing IOAPIC");
log_hex("[apic] index=", i);
ioapic_init_one(&info.ioapics[i], api->hhdm_offset,
info.isos, info.iso_count,
api->mm_map_mmio);
}
} else {
g_log->info("[apic] ioapic_init=0: skip IOAPIC programming (PIC mode)");
}
g_log->info("[apic] discovery + init complete; PIC routing retained as fallback");
/* ---- B7: 路由接管 (默认关, FUCK apic_route=1 启用) ---- */
if (g_cfg.apic_route && g_cfg.ioapic_init) {
apic_route_switch(api, &info);
} else {
g_log->info("[apic] apic_route=0: PIC routing retained (default)");
}
/* ---- B7 diag: IRQ12 鼠标流观察窗 (apic_diag=1, 两种路由模式皆可测) ---- */
if (g_cfg.apic_diag) {
apic_diag_mouse_window(api, &info);
}
/* 重定向表回读 (验证证据; 无 IOAPIC 时无输出) */
if (g_cfg.ioapic_init) {
ioapic_dump_table(&info);
}
if (g_route_active) {
g_log->info("[apic] discovery + init complete; APIC routing active");
} else {
g_log->info("[apic] discovery + init complete; PIC routing retained as fallback");
}
g_log->info("[apic] driver ready");
return 0;
}
+5
View File
@@ -193,6 +193,11 @@ struct dkm_kernel_api {
/* —— 新增字段只允许追加在末尾(DSK 以 api+0xA8 硬偏移读取 block) —— */
void *(*mm_map_mmio)(u64 phys, u64 size); /* MMIO 独立窗口映射(PCD|PWT),返回虚拟地址,失败返回 0 */
void (*mm_unmap_mmio)(void *virt, u64 size); /* 解除 MMIO 独立窗口映射 */
/* B7 阶段2 尾部追加(旧内核无此字段 → 判空使用) */
void (*register_apic_eoi)(void (*eoi_fn)(void));
/* B7 阶段3 尾部追加:动态 IDT 向量分配器(MSI/MSI-X),池 0x40-0xDF */
int (*irq_vector_alloc)(void);
void (*irq_vector_free)(int vector);
};
struct dkm_driver_handle;
+699 -6
View File
@@ -6,8 +6,17 @@
* 1. 64-bit BAR 常位于 4G 以上(QEMU/OVMF 把 NVMe BAR0 放到 ~48GiB),
* HHDM 直映射不保证覆盖。访问前先用 kernel_api.mmio->is_mapped 做
* 四级页表 walk 核实;未映射则 map_mmio 原地补建 4KiB UC 页。
* 2. 同步轮询模型: 单 outstanding 命令 + CQE phase bit 轮询 + TSC 超时,
* 不依赖 IRQ/MSI(当前 PIC/APIC 后端未完工)。
* 2. 完成模型: 单 outstanding 命令 + CQE phase bit。B7 阶段3 起 IO 队列
* 默认尝试 MSI/MSI-X 中断驱动(FUCK [drivers] nvme_msi, 默认 1 自动):
* 先 MSI cap 编程, 缺则 MSI-X (QEMU nvme 只暴露 MSI-X) — 表项
* addr=0xFEE00000|dest<<12, data=动态向量, entry unmask + cap Enable;
* Create IOCQ IEN=1/IV=0 + INTMC 解除 IV0; handler 摘 CQE 进完成槽、
* 推进 head、写 CQ doorbell, 自写 LAPIC EOI 并返回 1 (MSI 直投 LAPIC,
* 与 apic_route 开关无关, 不依赖阶段2 的 EOI 钩子注册状态)。
* 提交侧统一等待: 先查完成槽(中断路径), 再在 irqsave 临界区内手动
* 摘取(自适应轮询路径) — IF=0 (DSK 阶段) 或 MSI 未投递时零感知降级,
* 行为与旧版纯轮询一致。计数: g_msi_irq_count / g_msi_slot_hits /
* g_msi_poll_harvests / g_msi_timeouts, init 末尾与压测后打点。
* 3. 数据面: 32 页 (128KiB) 物理连续 DMA bounce buffer + 单页 PRP list。
* PRP 布局按传输页数分档: 1 页单 PRP1; 2 页 PRP2 直连第二页;
* >=3 页 PRP2 指向 PRP list 页 (512 项 u64, 上限 513 页 = 2052KiB)。
@@ -32,7 +41,7 @@ const struct dkm_driver_desc driver_desc = {
.abi_version = DKM_ABI_VERSION,
.desc_size = sizeof(struct dkm_driver_desc),
.name = "nvme",
.version = "0.3.0",
.version = "0.4.0",
.vendor = "Deshab",
.driver_class = 6, /* DKM_CLASS_STORAGE */
.stage = 1,
@@ -110,6 +119,24 @@ static u32 g_chunk_max_sectors; /* 单命令扇区上限 (buffer/MDTS/NL
static int g_io_ready;
static int g_test_disk; /* LBA0 带 DESHABNVME0 签名的测试盘 */
/* ---- B7 阶段3: MSI 状态 ---- */
static int g_cfg_nvme_msi = 1; /* FUCK [drivers] nvme_msi, 默认 1 自动尝试 */
static int g_msi_enabled; /* MSI/MSI-X 编程成功且 handler 已注册 */
static int g_msix_mode; /* 1=MSI-X 表项模式, 0=MSI cap 模式 */
static int g_msi_vector = -1; /* 动态分配的 IDT 向量 (0x40-0xDF 池) */
/* 完成槽: 单 outstanding 前提下, handler 是唯一写者、提交侧是唯一读者,
* 且下一命令提交前不可能再有新 CQE, 故读-清无需互斥; valid 最后写/最先读,
* x86 TSO store-store / load-load 天然有序, nvme_mb 只挡编译器重排。 */
static volatile u32 g_slot_valid;
static volatile u32 g_slot_status; /* sc | (sct << 8), 0 = 成功 */
static volatile u32 g_slot_dw0; /* CQE dw0 (命令特定返回) */
static volatile u32 g_slot_cid; /* CQE dw3 低 16 位 CID, 提交侧比对防错位 */
static u64 g_msi_irq_count; /* MSI handler 进入次数 (投递证明) */
static u64 g_msi_slot_hits; /* 提交侧经完成槽完成的命令数 (中断路径) */
static u64 g_msi_poll_harvests; /* 提交侧 irqsave 手动摘取数 (轮询路径) */
static u64 g_msi_timeouts; /* 完成超时计数 (真故障, 非降级) */
static u64 g_msi_cid_mismatch; /* 摘到 CID 与提交 cid 不符的 CQE 计数 (队列错位警报) */
/* ---- 基础工具 ---- */
static void log_hex(const char *prefix, u64 value) {
@@ -149,6 +176,66 @@ static __inline__ void nvme_mb(void) {
__asm__ volatile("" ::: "memory");
}
/* 十进制打印 (MSI 计数器打点用, 比 hex 直观) */
static void log_dec(const char *prefix, u64 val) {
char buf[24];
u32 pos = 0;
if (!val) {
buf[pos++] = '0';
} else {
char tmp[20];
u32 n = 0;
while (val) { tmp[n++] = (char)('0' + (val % 10)); val /= 10; }
while (n) buf[pos++] = tmp[--n];
}
buf[pos] = 0;
g_log->info(prefix);
g_log->info(buf);
}
static int memeq(const char *a, const char *b, u32 n) {
for (u32 i = 0; i < n; i++) {
if (a[i] != b[i]) return 0;
}
return 1;
}
static u32 str_len(const char *s) {
u32 n = 0;
while (s[n]) n++;
return n;
}
/* 在 NUL 结尾字符串中查找子串 (boot module cmdline 匹配用) */
static int str_contains(const char *hay, const char *needle) {
u32 nlen = str_len(needle);
if (!nlen) return 1;
for (u32 i = 0; hay[i]; i++) {
u32 j = 0;
while (j < nlen && hay[i + j] && hay[i + j] == needle[j]) j++;
if (j == nlen) return 1;
}
return 0;
}
static void nvme_cpuid(u32 leaf, u32 *a, u32 *b, u32 *c, u32 *d) {
__asm__ volatile ("cpuid"
: "=a"(*a), "=b"(*b), "=c"(*c), "=d"(*d)
: "a"(leaf), "c"(0));
}
/* irqsave 临界区: 提交侧手动摘取 CQE 时屏蔽中断, 与 MSI handler 互斥。
* pushf/popf 配对恢复 IF — DSK 阶段 IF=0, 绝不能无条件 sti。 */
static __inline__ u64 nvme_irqsave(void) {
u64 flags;
__asm__ volatile("pushfq; popq %0; cli" : "=r"(flags) :: "memory");
return flags;
}
static __inline__ void nvme_irqrestore(u64 flags) {
__asm__ volatile("pushq %0; popfq" :: "r"(flags) : "memory", "cc");
}
static u32 mmio_read32(u32 off) {
return *(volatile u32 *)(void *)(g_regs + off);
}
@@ -204,6 +291,138 @@ static int nvme_find(u8 *out_bus, u8 *out_dev, u8 *out_func) {
return -1;
}
/* ---- FUCK 配置自解析 (与 apic.c 同一范式, 不依赖 UTSM ini_parser) ----
* UTSM 经 Limine boot module (cmdline="fuck:config") 预加载 FUCK;
* kernel_api.boot_modules_response 对所有 DKM 驱动可见。
* 此处只找 [drivers] 分区下的 nvme_msi 键; 缺失文件/键一律默认 1
* (自动尝试 MSI, 失败回轮询), 与无配置时行为一致。
*/
/* Limine boot module 最小类型 (与 apic.c / bootfs.c / UTSM limine.h 布局一致) */
struct limine_file_min {
u64 revision;
void *address;
u64 size;
char *path;
char *cmdline;
u32 media_type;
u32 unused;
u32 tftp_ip;
u32 tftp_port;
u32 partition_index;
u32 mbr_disk_id;
u8 gpt_disk_uuid[16];
u8 gpt_part_uuid[16];
u8 part_uuid[16];
};
struct limine_module_response_min {
u64 revision;
u64 module_count;
struct limine_file_min **modules;
};
/* 解析值 token: 支持 0/1/true/false/on/off/yes/no 及十进制整数 */
static int parse_scalar(const char *line, u64 start, u64 line_len, int defval) {
u64 i = start;
while (i < line_len && (line[i] == ' ' || line[i] == '\t')) i++;
u64 tok_start = i;
while (i < line_len && line[i] != ' ' && line[i] != '\t' &&
line[i] != '#' && line[i] != ';' && line[i] != '\r') i++;
u64 tok_len = i - tok_start;
const char *tok = line + tok_start;
if (!tok_len) return defval;
if (tok_len == 1 && tok[0] == '1') return 1;
if (tok_len == 1 && tok[0] == '0') return 0;
if (tok_len == 4 && memeq(tok, "true", 4)) return 1;
if (tok_len == 5 && memeq(tok, "false", 5)) return 0;
if (tok_len == 2 && memeq(tok, "on", 2)) return 1;
if (tok_len == 3 && memeq(tok, "off", 3)) return 0;
if (tok_len == 3 && memeq(tok, "yes", 3)) return 1;
if (tok_len == 2 && memeq(tok, "no", 2)) return 0;
int neg = 0;
u64 j = 0;
if (tok[0] == '-') { neg = 1; j = 1; }
int val = 0;
int any = 0;
for (; j < tok_len; j++) {
if (tok[j] < '0' || tok[j] > '9') return defval;
val = val * 10 + (tok[j] - '0');
any = 1;
}
if (!any) return defval;
return neg ? -val : val;
}
static int fuck_get_int(const char *text, u64 size,
const char *section, const char *key, int defval) {
u64 pos = 0;
int in_section = 0;
u32 slen = str_len(section);
u32 klen = str_len(key);
while (pos < size) {
u64 end = pos;
while (end < size && text[end] != '\n') end++;
u64 line_len = end - pos;
if (line_len > 0 && text[pos + line_len - 1] == '\r') line_len--;
const char *line = text + pos;
u64 i = 0;
while (i < line_len && (line[i] == ' ' || line[i] == '\t')) i++;
if (i < line_len) {
char c = line[i];
if (c == '#' || c == ';') {
/* 注释行, 跳过 */
} else if (c == '[') {
in_section = 0;
if (line_len >= (u64)(i + 1 + slen + 1) &&
memeq(line + i + 1, section, slen) &&
line[i + 1 + slen] == ']') {
in_section = 1;
}
} else if (in_section) {
if (line_len >= (u64)(i + klen) &&
memeq(line + i, key, klen)) {
u64 j = i + klen;
while (j < line_len && (line[j] == ' ' || line[j] == '\t')) j++;
if (j < line_len && line[j] == '=') {
return parse_scalar(line, j + 1, line_len, defval);
}
}
}
}
pos = end + 1;
}
return defval;
}
static void nvme_load_config(const struct dkm_kernel_api *api) {
/* 编译期默认: 自动尝试 MSI (任何前提缺失都会回退纯轮询, 零回归) */
g_cfg_nvme_msi = 1;
const struct limine_module_response_min *resp =
(const struct limine_module_response_min *)api->boot_modules_response;
if (!resp || !resp->modules) {
g_log->warn("[nvme] no boot modules; FUCK defaults in effect");
return;
}
for (u64 i = 0; i < resp->module_count; i++) {
struct limine_file_min *f = resp->modules[i];
if (!f || !f->cmdline || !f->address || !f->size) continue;
if (!str_contains(f->cmdline, "fuck:config")) continue;
g_cfg_nvme_msi = fuck_get_int((const char *)f->address, f->size,
"drivers", "nvme_msi", 1);
g_log->info("[nvme] FUCK config module found");
log_dec("[nvme] cfg nvme_msi=", (u64)g_cfg_nvme_msi);
return;
}
g_log->warn("[nvme] FUCK module (fuck:config) not found; defaults in effect");
}
/* ---- CSTS 等待 ---- */
static int nvme_wait_csts(u32 mask, u32 expect, u64 timeout_ms) {
@@ -229,6 +448,142 @@ static int nvme_wait_csts(u32 mask, u32 expect, u64 timeout_ms) {
/* 前向声明: nvme_dump_regs 在 nvme_submit 中使用 */
static void nvme_dump_regs(const char *ctx);
/* ---- B7 阶段3: IO CQ 完成摘取 (MSI handler 与提交侧轮询共用) ----
* 返回 1 = 摘到一枚 CQE; 0 = 无新完成。
* out_cid 返回 CQE dw3 低 16 位的命令 ID, 供调用方与提交 cid 比对 —
* 单 outstanding 模型下 CQ 任何时刻只应有当前命令的一枚 CQE, CID 不匹配
* 即队列状态错位的硬证据, 必须显式报错而非静默当作当前命令完成
* (route=0 曾观测 stress readback MISMATCH @0x9000, 疑 CQE 提前消费)。
* 并发模型: 单生产者(控制器 DMA 写 CQ) + 单逻辑消费者, 消费者有两个入口 ——
* 1. MSI handler (IRQ 上下文): 同一 vector 在 EOI 前不会被 LAPIC 再次
* 投递, handler 对同向量天然不可重入, 无需自锁;
* 2. 提交侧统一等待 (主上下文): 必须在 nvme_irqsave 临界区内调用,
* 屏蔽中断期间 handler 不可能插入, 消除 "phase 判定后、head 推进前
* 被 handler 抢摘同一 CQE" 的双消费竞态。
* head 推进 + CQ doorbell 只在此函数内发生, 是两个入口的唯一汇合点。
*/
static int nvme_iocq_harvest(u32 *out_status, u32 *out_dw0, u32 *out_cid) {
volatile u32 *cq = (volatile u32 *)g_iocq.virt;
u32 dw3 = cq[g_iocq_head * 4 + 3];
if (((dw3 >> 16) & 1u) != g_iocq_phase) return 0;
if (out_dw0) *out_dw0 = cq[g_iocq_head * 4 + 0];
if (out_cid) *out_cid = dw3 & 0xffffu;
u32 sc = (dw3 >> 17) & 0xffu;
u32 sct = (dw3 >> 25) & 0x7u;
/* SC 与 SCT 联合编码 (与 nvme_submit 经典路径同一约定):
* SCT!=0 且 SC==0 时只返回 sc 会被误判成功。 */
*out_status = sc | (sct << 8);
g_iocq_head = (g_iocq_head + 1) % IO_QSIZE;
if (g_iocq_head == 0) g_iocq_phase ^= 1u;
nvme_mb(); /* head 先于 doorbell 可见 */
mmio_write32(cq_head_db(1), g_iocq_head);
return 1;
}
/* MSI 中断 handler: 摘 CQE 进完成槽。
* 返回 1 = 自负 LAPIC EOI (抑制 idt_handler 的钩子补 EOI)。
* 为何自写 EOI 而不依赖钩子: MSI 是设备向 LAPIC 直投的消息中断,
* 投递路径不经过 IOAPIC/PIC, 与 apic_route 开关无关; 但 EOI 钩子只在
* route=1 时注册, route=0 下钩子为 NULL 会漏写 EOI — LAPIC ISR 位
* 残留将永久阻塞同优先级后续中断, MSI 静默退化为 one-shot。
* handler 内直写 LAPIC EOI 寄存器后返回 1, 两种路由模式下行为一致。
* LAPIC 未软件使能时该写为无害 no-op (此时 MSI 本就不会投递)。
* 地址取 x86 默认 LAPIC base 0xFEE00000 + EOI 偏移 0xB0 (QEMU/MADT
* 一致; SAS-R0 恒等映射覆盖 4G 以下, apic.drv 已实证可直访)。
* 时序说明: 即使提交侧轮询抢先摘走 CQE, 控制器中断信号早已发出,
* 本次 handler 仍会执行并计入 g_msi_irq_count — 投递证据不受竞态影响。 */
static int nvme_msi_handler(u8 vector) {
(void)vector;
g_msi_irq_count++;
u32 st = 0, dw0 = 0, cid = 0;
if (nvme_iocq_harvest(&st, &dw0, &cid)) {
g_slot_status = st;
g_slot_dw0 = dw0;
g_slot_cid = cid;
nvme_mb(); /* payload 先于 valid 对提交侧可见 */
g_slot_valid = 1;
}
*(volatile u32 *)(uintptr_t)0xFEE000B0u = 0; /* LAPIC EOI */
return 1;
}
/* IO 命令统一完成等待 (MSI 模式): 完成槽(中断路径)优先, irqsave 手动摘取
* (自适应轮询路径)兜底。MSI 健康时 handler 在 CQE 落盘瞬间填槽; MSI 未投递
* (LAPIC 未启用/IF=0/信号丢失) 时手动摘取沿原 phase 语义直接完成, 调用方
* 零感知。超时语义与经典路径一致 (CAP.TO*500ms*10, 下限 5s)。 */
static int nvme_io_wait_completion(u16 cid, u32 opcode, u32 *out_cqe_dw0) {
u64 submit_timeout_ms = g_timeout_ms * 10;
if (submit_timeout_ms < 5000) submit_timeout_ms = 5000;
u64 deadline = dkm_rdtsc() + dkm_tsc_per_ms * submit_timeout_ms;
for (;;) {
/* 中断路径: handler 已摘取 CQE 进完成槽 */
if (g_slot_valid) {
u32 st = g_slot_status;
u32 dw0 = g_slot_dw0;
u32 scid = g_slot_cid;
nvme_mb();
g_slot_valid = 0;
g_msi_slot_hits++;
if (scid != (u32)cid) {
/* CID 错位: 槽内 CQE 不属于本命令 — 队列状态已损,
* 显式报错并继续等真 CQE (超时兜底), 绝不静默当成功 */
g_msi_cid_mismatch++;
g_log->error("[nvme] CID mismatch (slot)");
log_hex("[nvme] expect cid=", cid);
log_hex("[nvme] got cid=", scid);
continue;
}
if (out_cqe_dw0) *out_cqe_dw0 = dw0;
if (st) {
g_log->error("[nvme] command failed (msi slot)");
log_hex("[nvme] opcode=", opcode);
log_hex("[nvme] cid=", cid);
log_hex("[nvme] status=", st);
return (int)st;
}
return 0;
}
/* 自适应轮询路径: irqsave 临界区内手动摘取 (与 handler 互斥) */
u32 st = 0, dw0 = 0, hcid = 0;
u64 flags = nvme_irqsave();
int got = nvme_iocq_harvest(&st, &dw0, &hcid);
nvme_irqrestore(flags);
if (got) {
g_msi_poll_harvests++;
if (hcid != (u32)cid) {
g_msi_cid_mismatch++;
g_log->error("[nvme] CID mismatch (poll)");
log_hex("[nvme] expect cid=", cid);
log_hex("[nvme] got cid=", hcid);
continue;
}
if (out_cqe_dw0) *out_cqe_dw0 = dw0;
if (st) {
g_log->error("[nvme] command failed");
log_hex("[nvme] opcode=", opcode);
log_hex("[nvme] cid=", cid);
log_hex("[nvme] status=", st);
return (int)st;
}
return 0;
}
if (dkm_rdtsc() > deadline) {
g_msi_timeouts++;
g_log->error("[nvme] completion timeout (msi mode)");
log_hex("[nvme] opcode=", opcode);
log_hex("[nvme] cid=", cid);
nvme_dump_regs("completion timeout");
return -100;
}
__asm__ volatile("pause");
}
}
/* ---- 命令提交(同步轮询, 单 outstanding) ----
* dw[16] 为完整 SQE(dw0 低 16 位不含 CID, 由本函数填入)。
* 返回 0 成功;>0 = NVMe status code;<0 超时/内部错误。
@@ -239,6 +594,10 @@ static int nvme_submit(volatile u32 *sq, u32 *sq_tail, u16 sq_qid,
dw[0] = (dw[0] & 0xffffu) | ((u32)g_cid << 16);
u32 cid = g_cid;
g_cid++;
/* CID=0 保留不用: QEMU 在 MSI-X unmask 边界会向 IO CQ 补投一枚
* CID=0/phase 正确/status 成功的幽灵 CQE (实测稳定复现), g_cid 回绕
* 跳过 0 可保证该幽灵 CQE 永远被 CID 校验拒绝, 绝无误判窗口。 */
if (g_cid == 0) g_cid = 1;
/* 逐 dword 写入 SQE(64B),避免 freestanding 下编译器生成 memcpy 调用 */
u32 pos = *sq_tail;
@@ -248,6 +607,13 @@ static int nvme_submit(volatile u32 *sq, u32 *sq_tail, u16 sq_qid,
nvme_mb(); /* SQ 内容先于 doorbell 可见 */
mmio_write32(sq_tail_db(sq_qid), *sq_tail);
/* B7 阶段3: IO 队列 MSI 模式 → 完成槽 + irqsave 摘取统一等待。
* admin 队列保持纯轮询 (仅 init 期使用, 且 INTMC 解除前 IV0 处于
* 屏蔽态, admin 完成不会触发 MSI)。 */
if (cq_qid == 1 && g_msi_enabled) {
return nvme_io_wait_completion(cid, dw[0] & 0xffu, out_cqe_dw0);
}
/* 真机超时: 使用 CAP.TO * 500ms * 10 (留 10 倍余量), 下限 5s。
* 真机 NVMe 控制器在重负载下可能需要更长的完成时间。 */
u64 submit_timeout_ms = g_timeout_ms * 10;
@@ -371,19 +737,277 @@ static int nvme_identify_namespace(u32 nsid, u64 *out_nsze, u32 *out_lbads) {
return 0;
}
/* ---- B7 阶段3: PCI MSI 能力编程 ----
* MSI cap (ID 0x05) 布局:
* +0x00 u8 Cap ID (=0x05) +0x01 u8 Next Ptr
* +0x02 u16 Message Control: bit0 Enable, bit[3:1] MMC(可申请向量数-1),
* bit[6:4] MME(已分配向量数-1), bit7 64-bit Capable, bit8 PVM
* +0x04 u32 Message Address
* 64-bit 时: +0x08 u32 Message Upper Address, +0x0C u16 Message Data
* 32-bit 时: +0x08 u16 Message Data
* PVM(bit8)=1 时另有 Mask/Pending 寄存器, 复位值 0 (全部 unmask)。
* x86 LAPIC 投递公式 (physical destination, fixed delivery):
* Address = 0xFEE00000 | (dest_apic_id << 12) (RH=0, DM=0)
* Data = vector | (0<<8 delivery=fixed) (level/trigger 对 MSI 无效)
*/
#define PCI_CAP_ID_MSI 0x05
#define PCI_CAP_ID_MSIX 0x11
#define PCI_STATUS_CAPLIST 0x10u
static int nvme_find_pci_cap(u8 bus, u8 dev, u8 func, u8 cap_id) {
u32 reg04 = dkm_pci_read(bus, dev, func, PCI_COMMAND);
if (!((reg04 >> 16) & PCI_STATUS_CAPLIST)) return -1; /* Status.CapList=0 */
u8 ptr = (u8)(dkm_pci_read(bus, dev, func, PCI_CAP_PTR) & 0xFC);
int guard = 0;
while (ptr && guard++ < 64) { /* guard 防配置空间坏链死循环 */
u32 dw = dkm_pci_read(bus, dev, func, ptr);
u8 id = (u8)(dw & 0xff);
u8 next = (u8)((dw >> 8) & 0xff);
log_hex("[nvme] pci cap id=", id);
log_hex("[nvme] pci cap at=", ptr);
if (id == cap_id) return (int)ptr;
ptr = next & 0xFC;
}
return -1;
}
/* MSI cap (0x05) 编程路径: 成功返回 1, cap 缺失或失败返回 0。
* 失败路径完整回滚 (恢复 msg_ctl + 释放向量), 不留半编程状态。 */
static int nvme_msi_program(const struct dkm_kernel_api *api,
u8 bus, u8 dev, u8 func) {
int cap = nvme_find_pci_cap(bus, dev, func, PCI_CAP_ID_MSI);
if (cap < 0) {
g_log->info("[nvme] MSI capability not present");
return 0;
}
u32 cdw = dkm_pci_read(bus, dev, func, (u8)cap);
u32 msg_ctl = (cdw >> 16) & 0xffffu;
int cap64 = (int)((msg_ctl >> 7) & 1u);
int pvm = (int)((msg_ctl >> 8) & 1u);
log_hex("[nvme] MSI msg_ctl=", msg_ctl);
log_hex("[nvme] MSI 64bit=", (u64)cap64);
log_hex("[nvme] MSI pvm=", (u64)pvm);
/* 动态向量: UTSM 位图池 0x40-0xDF, 与静态向量 (tick 0xE0/spurious 0xFF)
* 及 legacy PIC 段 (0x20-0x2F) 无交叠。 */
int vector = api->irq_vector_alloc();
if (vector < 0 || vector > 255) {
g_log->warn("[nvme] MSI: vector alloc failed; polling");
return 0;
}
log_dec("[nvme] MSI vector=", (u64)vector);
/* 目的 APIC ID: CPUID.1:EBX[31:24] = BSP initial APIC ID (QEMU=0) */
u32 ca, cb, cc_, cd;
nvme_cpuid(1, &ca, &cb, &cc_, &cd);
(void)ca; (void)cc_; (void)cd;
u32 dest = (cb >> 24) & 0xffu;
log_hex("[nvme] MSI dest apic id=", dest);
u32 addr = 0xFEE00000u | (dest << 12);
u32 data = (u32)vector & 0xffu; /* fixed delivery, edge */
/* 先写 Address/Data, 最后置 Enable — 避免半成品消息被发出 */
dkm_pci_write(bus, dev, func, (u8)(cap + 4), addr);
if (cap64) {
dkm_pci_write(bus, dev, func, (u8)(cap + 8), 0); /* upper addr */
u32 dw = dkm_pci_read(bus, dev, func, (u8)(cap + 12));
dw = (dw & 0xffff0000u) | data; /* data 为低 u16 */
dkm_pci_write(bus, dev, func, (u8)(cap + 12), dw);
} else {
u32 dw = dkm_pci_read(bus, dev, func, (u8)(cap + 8));
dw = (dw & 0xffff0000u) | data;
dkm_pci_write(bus, dev, func, (u8)(cap + 8), dw);
}
/* Message Control: MME=0 (单向量), Enable=1; 低 u16 (id/next) 回写原值 */
u32 mc = msg_ctl;
mc &= ~(0x7u << 4); /* MME=0 → 1 vector */
mc |= 1u; /* MSI Enable */
cdw = (cdw & 0x0000ffffu) | (mc << 16);
dkm_pci_write(bus, dev, func, (u8)cap, cdw);
/* PVM: 显式清 mask bit0 (复位值即为 0, 此处防御性 RMW) */
if (pvm) {
int mask_off = cap64 ? cap + 16 : cap + 12;
u32 mask = dkm_pci_read(bus, dev, func, (u8)mask_off);
mask &= ~1u;
dkm_pci_write(bus, dev, func, (u8)mask_off, mask);
log_hex("[nvme] MSI mask cleared=", mask);
}
/* 回读验证: Enable 置位 + Address/Data 与编程值一致 */
u32 rb_ctl = (dkm_pci_read(bus, dev, func, (u8)cap) >> 16) & 0xffffu;
u32 rb_addr = dkm_pci_read(bus, dev, func, (u8)(cap + 4));
u32 rb_data = dkm_pci_read(bus, dev, func,
(u8)(cap64 ? cap + 12 : cap + 8)) & 0xffffu;
log_hex("[nvme] MSI readback ctl=", rb_ctl);
log_hex("[nvme] MSI readback addr=", rb_addr);
log_hex("[nvme] MSI readback data=", rb_data);
if (!(rb_ctl & 1u) || rb_addr != addr || (rb_data & 0xffu) != data) {
g_log->warn("[nvme] MSI: readback mismatch; rolling back");
u32 rb = dkm_pci_read(bus, dev, func, (u8)cap);
rb = (rb & 0x0000ffffu) | (msg_ctl << 16); /* 恢复原 msg_ctl */
dkm_pci_write(bus, dev, func, (u8)cap, rb);
api->irq_vector_free(vector);
return 0;
}
/* 注册 handler (irq_register 已接受向量命名空间, >0x2F 直挂分发表) */
api->irq_register((u8)vector, (void *)(uintptr_t)nvme_msi_handler);
g_msi_enabled = 1;
g_msi_vector = vector;
g_log->info("[nvme] MSI enabled (IO CQ IEN=1, IV=0)");
return 1;
}
/* 按 BIR 读取 BAR 物理基址 (处理 64-bit BAR; IO BAR 返回 0) */
static u64 nvme_bar_phys(u8 bus, u8 dev, u8 func, u32 bir) {
u8 reg = (u8)(PCI_BAR0 + bir * 4u);
u32 lo = dkm_pci_read(bus, dev, func, reg);
if (lo & 1u) return 0; /* IO BAR */
if ((lo & 0x6u) == 0x4u) { /* 64-bit MMIO BAR */
u32 hi = dkm_pci_read(bus, dev, func, (u8)(reg + 4));
return ((u64)hi << 32) | (u64)(lo & 0xFFFFFFF0u);
}
return (u64)(lo & 0xFFFFFFF0u);
}
/* MSI-X cap (0x11) 编程路径: 成功返回 1, cap 缺失或失败返回 0。
* QEMU nvme 只暴露 MSI-X (无 0x05), 本路径是 QEMU 下中断驱动化的唯一通道。
* 与 MSI 的差异仅在消息存储位置: MSI-X 的 addr/data 存放在 BAR 内的
* 表项 (16B/项), 需先把表区 MMIO 映射进 CPU 地址空间再编程 entry 0。
* 失败路径完整回滚 (清 Enable + 表项重新 mask + 释放向量)。 */
static int nvme_msix_program(const struct dkm_kernel_api *api,
u8 bus, u8 dev, u8 func) {
int cap = nvme_find_pci_cap(bus, dev, func, PCI_CAP_ID_MSIX);
if (cap < 0) {
g_log->warn("[nvme] MSI-X capability not present; polling");
return 0;
}
if (!api->mm_map_mmio) {
g_log->warn("[nvme] MSI-X: mm_map_mmio unavailable; polling");
return 0;
}
u32 cdw = dkm_pci_read(bus, dev, func, (u8)cap);
u32 msg_ctl = (cdw >> 16) & 0xffffu;
u32 table_size = (msg_ctl & 0x7ffu) + 1u; /* N-1 encoded */
u32 tbl_dw = dkm_pci_read(bus, dev, func, (u8)(cap + 4));
u32 bir = tbl_dw & 7u;
u32 tbl_off = tbl_dw & ~7u;
log_hex("[nvme] MSI-X msg_ctl=", msg_ctl);
log_dec("[nvme] MSI-X table size=", table_size);
log_hex("[nvme] MSI-X table BIR=", bir);
log_hex("[nvme] MSI-X table offset=", tbl_off);
if (bir > 5u) {
g_log->warn("[nvme] MSI-X: invalid BIR; polling");
return 0;
}
u64 bar_phys = nvme_bar_phys(bus, dev, func, bir);
if (!bar_phys) {
g_log->warn("[nvme] MSI-X: table BAR invalid; polling");
return 0;
}
u64 tbl_phys = bar_phys + tbl_off;
log_hex("[nvme] MSI-X table BAR phys=", bar_phys);
log_hex("[nvme] MSI-X table phys=", tbl_phys);
/* 映射表区首页 (含 entry0 的 16B)。mm_map_mmio 返回含页内偏移的指针,
* 页属性 PCD|PWT (UC), MSI-X 表访问不要求 cache 一致性操作。 */
volatile u32 *tbl = (volatile u32 *)api->mm_map_mmio(tbl_phys, 4096);
if (!tbl) {
g_log->warn("[nvme] MSI-X: table MMIO map failed; polling");
return 0;
}
int vector = api->irq_vector_alloc();
if (vector < 0 || vector > 255) {
g_log->warn("[nvme] MSI-X: vector alloc failed; polling");
return 0;
}
log_dec("[nvme] MSI-X vector=", (u64)vector);
u32 ca, cb, cc_, cd;
nvme_cpuid(1, &ca, &cb, &cc_, &cd);
(void)ca; (void)cc_; (void)cd;
u32 dest = (cb >> 24) & 0xffu;
u32 addr = 0xFEE00000u | (dest << 12);
/* 先写表项 (addr/data/mask=0), 再置 cap Enable — 避免半成品消息 */
volatile u32 *e = tbl; /* entry 0 */
e[0] = addr; /* Msg Addr Lo */
e[1] = 0; /* Msg Addr Hi */
e[2] = (u32)vector & 0xffu; /* Msg Data = 向量 */
e[3] = 0; /* Vector Control: unmask */
nvme_mb();
u32 nc = (msg_ctl | 0x8000u) & ~(1u << 14); /* Enable=1, FunctionMask=0 */
cdw = (cdw & 0x0000ffffu) | (nc << 16);
dkm_pci_write(bus, dev, func, (u8)cap, cdw);
/* 回读验证: cap Enable + 表项 addr/data/mask 与编程值一致 */
u32 rb_ctl = (dkm_pci_read(bus, dev, func, (u8)cap) >> 16) & 0xffffu;
u32 rb_a0 = e[0], rb_a1 = e[1], rb_d = e[2], rb_vc = e[3];
log_hex("[nvme] MSI-X readback ctl=", rb_ctl);
log_hex("[nvme] MSI-X readback entry addr=", ((u64)rb_a1 << 32) | rb_a0);
log_hex("[nvme] MSI-X readback entry data=", rb_d);
log_hex("[nvme] MSI-X readback entry vctl=", rb_vc);
if (!(rb_ctl & 0x8000u) || rb_a0 != addr || rb_a1 != 0 ||
(rb_d & 0xffu) != ((u32)vector & 0xffu) || (rb_vc & 1u)) {
g_log->warn("[nvme] MSI-X: readback mismatch; rolling back");
u32 rb = dkm_pci_read(bus, dev, func, (u8)cap);
rb = (rb & 0x0000ffffu) | (msg_ctl << 16); /* 恢复原 msg_ctl */
dkm_pci_write(bus, dev, func, (u8)cap, rb);
e[3] = 1; /* 表项重新 mask */
api->irq_vector_free(vector);
return 0;
}
api->irq_register((u8)vector, (void *)(uintptr_t)nvme_msi_handler);
g_msi_enabled = 1;
g_msix_mode = 1;
g_msi_vector = vector;
g_log->info("[nvme] MSI-X enabled (table entry0, IO CQ IEN=1, IV=0)");
return 1;
}
/* 中断驱动化入口: 先 MSI (0x05), 缺则 MSI-X (0x11), 皆不可则纯轮询。
* 前提检查集中在此; 两条编程路径各自保证失败完整回滚。 */
static int nvme_msi_setup(const struct dkm_kernel_api *api,
u8 bus, u8 dev, u8 func) {
if (!g_cfg_nvme_msi) {
g_log->info("[nvme] MSI disabled by FUCK (nvme_msi=0); polling mode");
return 0;
}
if (!api->irq_vector_alloc || !api->irq_vector_free || !api->irq_register) {
g_log->warn("[nvme] MSI: kernel vector allocator unavailable; polling");
return 0;
}
if (nvme_msi_program(api, bus, dev, func)) return 1;
return nvme_msix_program(api, bus, dev, func);
}
/* ---- IO queue 创建 ---- */
static int nvme_create_io_queues(void) {
u32 dw[16];
/* Create IO CQ (qid=1): PC=1, IEN=0 (轮询模式)
* CDW10 = QSIZE[31:16](0's based) | QID[15:0] —— QID 必须在低半字 */
/* Create IO CQ (qid=1): PC=1; MSI 就绪时 IEN=1 + IV=0
* CDW10 = QSIZE[31:16](0's based) | QID[15:0] —— QID 必须在低半字
* CDW11 = IV[31:16] | IEN[1] | PC[0] —— IV=0 与 INTMS/INTMC bit0 对应 */
nvme_zero(dw, sizeof(dw));
dw[0] = NVME_ADM_CREATE_IOCQ;
dw[6] = (u32)(g_iocq.phys & 0xffffffffu);
dw[7] = (u32)(g_iocq.phys >> 32);
dw[10] = ((u32)(IO_QSIZE - 1u) << 16) | 1u;
dw[11] = 1;
dw[11] = 1u;
if (g_msi_enabled) dw[11] = 1u | (1u << 1); /* PC=1, IEN=1, IV=0 */
log_hex("[nvme] create IOCQ CDW11=", dw[11]);
int rc = nvme_admin_submit(dw, 0);
if (rc != 0) {
g_log->error("[nvme] create IO CQ failed");
@@ -586,6 +1210,61 @@ static void nvme_selftest_rw(u64 lba, u32 sectors) {
g_log->info("[nvme] write+flush+readback verify OK");
}
/* B7 阶段3 压测: 128 扇区 (64KiB, PRP list 路径) × 50 次 写+读。
* 统计总耗时与 MSI 计数器增量, 供中断/轮询两模式粗粒度对比。
* 仅签名测试盘运行 (写 LBA3000 起的 50 个块, 不碰 LBA0 签名区)。 */
static void nvme_selftest_stress(void) {
static u8 wbuf[65536];
static u8 rbuf[65536];
for (u32 i = 0; i < sizeof(wbuf); i++) wbuf[i] = (u8)(i * 17u + 3u);
u64 irq0 = g_msi_irq_count, slot0 = g_msi_slot_hits, poll0 = g_msi_poll_harvests;
u64 t0 = dkm_rdtsc();
u32 fails = 0;
for (u32 i = 0; i < 50; i++) {
u64 lba = 3000 + (u64)i * 128;
if (nvme_block_write(0, lba, 128, wbuf) != 0) { fails++; break; }
if (nvme_block_read(0, lba, 128, rbuf) != 0) { fails++; break; }
}
u64 elapsed_ms = dkm_tsc_per_ms ? (dkm_rdtsc() - t0) / dkm_tsc_per_ms : 0;
/* 末次读回全量校验 (数据通路在 MSI 模式下完整性证明) */
if (!fails) {
for (u32 i = 0; i < sizeof(wbuf); i++) {
if (rbuf[i] != wbuf[i]) {
g_log->error("[nvme] stress readback MISMATCH");
log_hex("[nvme] first bad idx=", i);
log_hex("[nvme] expect=", wbuf[i]);
log_hex("[nvme] actual=", rbuf[i]);
log_hex("[nvme] cq head=", g_iocq_head);
log_hex("[nvme] cq phase=", g_iocq_phase);
log_hex("[nvme] slot valid=", g_slot_valid);
fails++;
break;
}
}
}
log_dec("[nvme] stress 50x(128sec W+R) ms=", elapsed_ms);
log_dec("[nvme] stress fails=", fails);
log_dec("[nvme] stress irq delta=", g_msi_irq_count - irq0);
log_dec("[nvme] stress slot delta=", g_msi_slot_hits - slot0);
log_dec("[nvme] stress poll delta=", g_msi_poll_harvests - poll0);
if (!fails) g_log->info("[nvme] stress verify OK");
}
static void nvme_msi_stats_dump(const char *tag) {
g_log->info(tag);
log_dec("[nvme] msi enabled=", (u64)g_msi_enabled);
log_dec("[nvme] msix mode=", (u64)g_msix_mode);
log_dec("[nvme] msi vector=", (u64)(g_msi_vector < 0 ? 0 : g_msi_vector));
log_dec("[nvme] msi irq count=", g_msi_irq_count);
log_dec("[nvme] msi slot hits=", g_msi_slot_hits);
log_dec("[nvme] msi poll harvests=", g_msi_poll_harvests);
log_dec("[nvme] msi timeouts=", g_msi_timeouts);
log_dec("[nvme] msi cid mismatch=", g_msi_cid_mismatch);
}
/* ---- 真机诊断: dump 关键寄存器 ---- */
static void nvme_dump_regs(const char *ctx) {
@@ -673,6 +1352,7 @@ int driver_init(const struct dkm_kernel_api *api,
dkm_instr_init(api);
g_log->info("[nvme] init begin");
nvme_load_config(api);
u8 bus = 0, dev = 0, func = 0;
if (nvme_find(&bus, &dev, &func) != 0) {
@@ -867,6 +1547,9 @@ dma_ok:
log_hex("[nvme] chunk max sectors=", g_chunk_max_sectors);
}
/* ---- B7 阶段3: IO queue 创建前尝试 MSI (任何前提缺失回退纯轮询) ---- */
nvme_msi_setup(api, bus, dev, func);
/* ---- IO queue ---- */
if (nvme_create_io_queues() != 0) {
g_log->error("[nvme] IO queue creation failed");
@@ -876,6 +1559,13 @@ dma_ok:
g_io_ready = 1;
g_log->info("[nvme] IO queue live");
/* MSI 就绪: INTMC 解除 IV0 屏蔽 (此前 IV0 保持屏蔽, admin 完成不产生
* MSI; 此后 IO CQ 每次完成经 MSI 投递到 g_msi_vector) */
if (g_msi_enabled) {
mmio_write32(NVME_INTMC, 1u);
log_hex("[nvme] INTMC unmask IV0, INTMS=", mmio_read32(NVME_INTMS));
}
/* ---- LBA0 读取验证 ---- */
{
int rc = nvme_io_rw(NVME_IO_READ, 0, 1, g_data.phys);
@@ -921,6 +1611,7 @@ dma_ok:
nvme_selftest_read_multipage(); /* 32 扇区 16KiB: PRP list 读 */
nvme_selftest_rw(1000, 16); /* 8KiB: PRP2 直连 (2 页) 写+读回 */
nvme_selftest_rw(2000, 32); /* 16KiB: PRP list (4 页) 写+读回 */
nvme_selftest_stress(); /* B7: 128 扇区×50 压测 + MSI 计数 */
if (nvme_lba0_sig_ok()) {
g_log->info("[nvme] LBA0 sig intact after write tests");
} else {
@@ -930,6 +1621,8 @@ dma_ok:
g_log->info("[nvme] not a test disk; write self-test skipped");
}
nvme_msi_stats_dump("[nvme] --- MSI stats ---");
g_log->info("[nvme] driver ready");
return 0;
}