- UTSM panic_full returns in fallback mode (logs [PANIC:FALLBACK] + code + hit count); panic_halt keeps always-halt sites (CPU exception, VMM-E11 LA57, VMM-E15 DSK jump, UTSM-E42 test hook). Call sites given safe continuations. - Mode passed to DSK/FirstInit/login/desktop via DSK_BOOT_FLAG_PANIC_FALLBACK; DKM kernel_api.panic / kern.log.panic return to driver; ext4 fatal aborts the op via setjmp/longjmp and DSK falls back to FAT32 ESP. - build.ps1/Makefile: dev=strict, release=fallback, new realtest (release options + strict); default -Variant all; serialized iso packing under -j. - Fix '*/' inside comments (dkm.h, kernel_api.h, bus.c) that broke the build. - Includes pending work: dkm_bus service bus, mt7920/wlan drivers, desktop files/startmenu/prank, pe-tools, diskinfo, docs. Co-authored-by: Cursor <cursoragent@cursor.com>
2159 lines
81 KiB
C
2159 lines
81 KiB
C
/* DKM xHCI Driver — USB xHCI host controller + Mass Storage Class (BOT/SCSI)
|
||
*
|
||
* Stage 1 optional storage driver, depends on "pci" and "irq", provides "block".
|
||
*
|
||
* Architecture:
|
||
* 1. PCI discovery: class=0x0C/03/30 (Serial Bus / USB / xHCI)
|
||
* 2. MMIO mapping: 64-bit BAR0 via mm_map_mmio or HHDM fallback
|
||
* 3. Controller init: BIOS handoff, HC reset, parse capabilities
|
||
* 4. Ring setup: Command Ring, Event Ring, ERST, DCBAA, Scratchpad
|
||
* 5. Port scan: detect connected devices, port reset
|
||
* 6. USB enumeration: Enable Slot, Address Device, GET_DESCRIPTOR
|
||
* 7. MSC protocol: BOT (Bulk-Only Transport) + SCSI commands
|
||
* 8. Block provider: register usb0 via kernel_api.block
|
||
*
|
||
* Synchronous polling model (no IRQ/MSI in first version).
|
||
* All hardware waits use TSC deadline — no infinite loops.
|
||
*/
|
||
|
||
#include "../dkm_shared.h"
|
||
#include "../dkm_instr.h"
|
||
|
||
DKM_STAT_DECL(xhci_pci_found);
|
||
DKM_STAT_DECL(xhci_port_reset_ok);
|
||
DKM_STAT_DECL(xhci_msc_found);
|
||
DKM_STAT_DECL(xhci_block_reads);
|
||
DKM_STAT_DECL(xhci_block_writes);
|
||
|
||
static const char *const g_depends[] = { "pci", "irq" };
|
||
static const char *const g_provides[] = { "block" };
|
||
|
||
__attribute__((visibility("default"), used))
|
||
const struct dkm_driver_desc driver_desc = {
|
||
.magic = DKM_DRIVER_MAGIC,
|
||
.abi_version = DKM_ABI_VERSION,
|
||
.desc_size = sizeof(struct dkm_driver_desc),
|
||
.name = "xhci",
|
||
.version = "0.1.0",
|
||
.vendor = "Deshab",
|
||
.driver_class = 6, /* DKM_CLASS_STORAGE */
|
||
.stage = 1,
|
||
.flags = 0,
|
||
.priority = 0,
|
||
.depends = g_depends,
|
||
.depends_count = 2,
|
||
.provides = g_provides,
|
||
.provides_count = 1,
|
||
.min_kernel_abi = 1,
|
||
.feature_bits = 0,
|
||
.reserved0 = 0,
|
||
.reserved1 = 0
|
||
};
|
||
|
||
/* ================================================================
|
||
* Constants — PCI, xHCI registers, TRB types, USB
|
||
* ================================================================ */
|
||
|
||
#define XHCI_CLASS_SERIAL 0x0C
|
||
#define XHCI_SUBCLASS_USB 0x03
|
||
#define XHCI_PROGIF_XHCI 0x30
|
||
|
||
/* Capability Registers (MMIO + 0x00) */
|
||
#define XHCI_CAP_HCSPARAMS1 0x00
|
||
#define XHCI_CAP_HCSPARAMS2 0x04
|
||
#define XHCI_CAP_HCSPARAMS3 0x08
|
||
#define XHCI_CAP_HCCPARAMS1 0x0C
|
||
#define XHCI_CAP_DBOFF 0x14
|
||
#define XHCI_CAP_RTSOFF 0x18
|
||
|
||
/* Operational Registers (MMIO + cap_length) */
|
||
#define XHCI_OP_USBCMD 0x00
|
||
#define XHCI_OP_USBSTS 0x04
|
||
#define XHCI_OP_PAGESIZE 0x08
|
||
#define XHCI_OP_DNCTRL 0x14
|
||
#define XHCI_OP_CRCR_LO 0x18
|
||
#define XHCI_OP_CRCR_HI 0x1C
|
||
#define XHCI_OP_DCBAAP_LO 0x30
|
||
#define XHCI_OP_DCBAAP_HI 0x34
|
||
#define XHCI_OP_CONFIG 0x38
|
||
|
||
/* Port Registers (MMIO + 0x400 + port * 0x10) */
|
||
#define XHCI_PORT_BASE_OFF 0x400
|
||
#define XHCI_PORT_STRIDE 0x10
|
||
#define XHCI_PORT_SC 0x00
|
||
|
||
/* Runtime Registers (MMIO + rtsoff) */
|
||
#define XHCI_RT_MFINDEX 0x00
|
||
#define XHCI_RT_IR0_IMAN 0x20
|
||
#define XHCI_RT_IR0_IMOD 0x24
|
||
#define XHCI_RT_IR0_ERSTSZ 0x28
|
||
#define XHCI_RT_IR0_ERSTBA_LO 0x30
|
||
#define XHCI_RT_IR0_ERSTBA_HI 0x34
|
||
#define XHCI_RT_IR0_ERDP_LO 0x38
|
||
#define XHCI_RT_IR0_ERDP_HI 0x3C
|
||
|
||
/* ERDP bits: bit3 = Event Handler Busy (软件写 1 清挂起) */
|
||
#define ERDP_EHB (1u << 3)
|
||
|
||
/* USBCMD bits */
|
||
#define USBCMD_RS (1u << 0)
|
||
#define USBCMD_RST (1u << 1)
|
||
#define USBCMD_INTE (1u << 2)
|
||
|
||
/* USBSTS bits */
|
||
#define USBSTS_HCH (1u << 0)
|
||
#define USBSTS_EINT (1u << 3)
|
||
#define USBSTS_CNR (1u << 11)
|
||
|
||
/* PORTSC bits */
|
||
#define PORTSC_CCS (1u << 0)
|
||
#define PORTSC_PED (1u << 1)
|
||
#define PORTSC_OCA (1u << 3)
|
||
#define PORTSC_PR (1u << 4)
|
||
#define PORTSC_PLS_SHIFT 5
|
||
#define PORTSC_PLS_MASK 0xF
|
||
/* Port Speed 字段在 PORTSC bits[13:10](xHCI 1.2 §5.4.8)。
|
||
* BUG-FIX: 原实现 shift=17/mask=0x7 是错的——QEMU 用 10:13,
|
||
* 误读导致 SuperSpeed(4) 被当成 FULL(1),EP0 MPS 错误。 */
|
||
#define PORTSC_SPEED_SHIFT 10
|
||
#define PORTSC_SPEED_MASK 0xF
|
||
|
||
/* TRB types */
|
||
#define TRB_TYPE_NORMAL 0x01
|
||
#define TRB_TYPE_SETUP_STAGE 0x02
|
||
#define TRB_TYPE_DATA_STAGE 0x03
|
||
#define TRB_TYPE_STATUS_STAGE 0x04
|
||
#define TRB_TYPE_LINK 0x06
|
||
#define TRB_TYPE_ENABLE_SLOT 0x09
|
||
#define TRB_TYPE_DISABLE_SLOT 0x0A
|
||
#define TRB_TYPE_ADDRESS_DEV 0x0B
|
||
#define TRB_TYPE_CONFIGURE_EP 0x0C
|
||
#define TRB_TYPE_NOOP 0x17
|
||
|
||
/* Event TRB types — xHCI 1.2 spec Table 6-28 (TRB Type field [15:10]).
|
||
* 规范值:Transfer=0x20, Command Completion=0x21, Port Status Change=0x22。
|
||
* BUG-FIX: 原定义 0x01/0x02/0x21 与规范冲突——CMD_COMPLETE(0x21) 被
|
||
* 误判为 PORT_CHANGE 事件消费,导致 Enable Slot 等命令事件环被提前
|
||
* 清空,后续命令全部超时(QEMU hcd-xhci.c xhci_write_event 佐证)。 */
|
||
#define EVT_TYPE_TRANSFER 0x20 /* Transfer Event */
|
||
#define EVT_TYPE_CMD_COMPLETE 0x21 /* Command Completion Event */
|
||
#define EVT_TYPE_PORT_CHANGE 0x22 /* Port Status Change Event */
|
||
|
||
/* TRB control field helpers */
|
||
#define TRB_TYPE_SHIFT 10
|
||
#define TRB_TYPE_MASK 0x3F
|
||
#define TRB_IOC (1u << 5) /* Interrupt On Completion */
|
||
#define TRB_CH (1u << 6) /* Chain bit */
|
||
#define TRB_ENT (1u << 7) /* Evaluate Next TRB (for Link) */
|
||
#define TRB_ISP (1u << 8) /* Interrupt on Short Packet */
|
||
#define TRB_CYCLE (1u << 0)
|
||
#define TRB_TC (1u << 1) /* Toggle Cycle (for Link) */
|
||
|
||
/* Setup Stage TRB direction */
|
||
#define SETUP_DIR_OUT 0
|
||
#define SETUP_DIR_IN 1
|
||
|
||
/* USB speeds */
|
||
#define USB_SPEED_FULL 1
|
||
#define USB_SPEED_LOW 2
|
||
#define USB_SPEED_HIGH 3
|
||
#define USB_SPEED_SUPER 4
|
||
|
||
/* Endpoint types for EP Context */
|
||
#define EP_TYPE_CTRL_OUT 0
|
||
#define EP_TYPE_CTRL_IN 4
|
||
#define EP_TYPE_BULK_OUT 2
|
||
#define EP_TYPE_BULK_IN 6
|
||
|
||
/* Slot states */
|
||
#define SLOT_STATE_DISABLED 0
|
||
#define SLOT_STATE_ENABLED 1
|
||
#define SLOT_STATE_ADDRESSED 2
|
||
#define SLOT_STATE_CONFIGURED 3
|
||
|
||
/* Ring sizes */
|
||
#define CMD_RING_SIZE 256
|
||
#define EVT_RING_SIZE 256
|
||
#define XFER_RING_SIZE 256
|
||
|
||
/* xHCI MMIO mapping size (64 KB covers most controllers) */
|
||
#define XHCI_MAP_SIZE 0x10000
|
||
|
||
/* BOT (Bulk-Only Transport) signatures */
|
||
#define BOT_CBW_SIG 0x43425355u /* "USBC" */
|
||
#define BOT_CSW_SIG 0x53425355u /* "USBS" */
|
||
|
||
/* SCSI opcodes */
|
||
#define SCSI_TEST_UNIT_READY 0x00
|
||
#define SCSI_INQUIRY 0x12
|
||
#define SCSI_READ_CAPACITY10 0x25
|
||
#define SCSI_READ10 0x28
|
||
#define SCSI_WRITE10 0x2A
|
||
|
||
/* ================================================================
|
||
* Data structures
|
||
* ================================================================ */
|
||
|
||
typedef struct {
|
||
u64 parameter; /* DW0-DW1 */
|
||
u32 status; /* DW2 */
|
||
u32 control; /* DW3 */
|
||
} xhci_trb;
|
||
|
||
/* TRB Ring */
|
||
typedef struct {
|
||
struct dkm_dma_buffer dma;
|
||
u32 size; /* TRB count */
|
||
u32 enqueue; /* producer index */
|
||
u32 ccs; /* producer Cycle State */
|
||
} xhci_ring;
|
||
|
||
/* ERST entry */
|
||
typedef struct {
|
||
u64 seg_addr;
|
||
u32 seg_size;
|
||
u32 rsvd;
|
||
} xhci_erst_entry;
|
||
|
||
/* MSC device state */
|
||
typedef struct {
|
||
u8 slot_id;
|
||
u8 speed;
|
||
u8 bulk_out_ep; /* EP number for Bulk OUT */
|
||
u8 bulk_in_ep; /* EP number for Bulk IN */
|
||
u16 bulk_out_mps; /* Max Packet Size */
|
||
u16 bulk_in_mps; /* Max Packet Size */
|
||
u32 cbw_tag; /* BOT tag counter */
|
||
u64 sector_count;
|
||
u32 sector_size;
|
||
int ready;
|
||
} xhci_msc_dev;
|
||
|
||
/* ================================================================
|
||
* Global state
|
||
* ================================================================ */
|
||
|
||
static const struct dkm_kernel_api *g_api;
|
||
static const struct dkm_log_api *g_log;
|
||
static volatile u8 *g_mmio; /* MMIO base virtual address */
|
||
static u32 g_cap_length; /* Capability register space length */
|
||
static u32 g_doorbell_off; /* Doorbell register offset */
|
||
static u32 g_runtime_off; /* Runtime register space offset */
|
||
static u32 g_max_slots;
|
||
static u32 g_max_ports;
|
||
static u32 g_max_intrs;
|
||
static u32 g_ctx_size; /* 32 or 64 bytes */
|
||
static u32 g_page_size; /* controller page size in bytes */
|
||
static u32 g_spb_count; /* scratchpad buffer count */
|
||
static u32 g_port_base; /* absolute MMIO offset of port register set (cap_length + 0x400) */
|
||
static u32 g_ppc; /* Per-Port Power Control (HCCPARAMS1 bit 3) */
|
||
|
||
static xhci_ring g_cmd_ring;
|
||
static xhci_ring g_evt_ring;
|
||
static u32 g_evt_ccs; /* consumer cycle state for event ring */
|
||
|
||
static struct dkm_dma_buffer g_erst; /* ERST DMA buffer */
|
||
static struct dkm_dma_buffer g_dcbaa; /* DCBAA DMA buffer */
|
||
static struct dkm_dma_buffer g_sp_bufs; /* Scratchpad buffer array */
|
||
/* Scratchpad individual pages — max 32 scratchpads for safety */
|
||
static struct dkm_dma_buffer g_sp_pages[32];
|
||
|
||
/* Per-device DMA buffers */
|
||
static struct dkm_dma_buffer g_input_ctx; /* Input Context */
|
||
static struct dkm_dma_buffer g_dev_ctx; /* Device Context (output) */
|
||
static xhci_ring g_ep0_ring; /* EP0 control transfer ring */
|
||
static xhci_ring g_bulk_out_ring;
|
||
static xhci_ring g_bulk_in_ring;
|
||
static struct dkm_dma_buffer g_xfer_buf; /* Data transfer bounce buffer */
|
||
static struct dkm_dma_buffer g_csw_buf; /* CSW receive buffer */
|
||
|
||
static xhci_msc_dev g_msc;
|
||
|
||
/* PCI device location */
|
||
static u8 g_pci_bus, g_pci_dev, g_pci_func;
|
||
|
||
/* ================================================================
|
||
* Utility functions
|
||
* ================================================================ */
|
||
|
||
static void log_hex(const char *prefix, u64 value) {
|
||
static const char hex[] = "0123456789abcdef";
|
||
char buf[19];
|
||
u32 pos = 0;
|
||
buf[pos++] = '0';
|
||
buf[pos++] = 'x';
|
||
for (int i = 15; i >= 0; i--)
|
||
buf[pos++] = hex[(value >> (i * 4)) & 0xf];
|
||
buf[pos] = 0;
|
||
g_log->info(prefix);
|
||
g_log->info(buf);
|
||
}
|
||
|
||
/* ================================================================
|
||
* Framebuffer 阶段诊断(实机无串口时屏幕直接显示 xhci 失败点)
|
||
* 失败时整屏填背景色 + 中央画大号两位十六进制状态码,然后 halt。
|
||
* 码表(调用点注释中注明):
|
||
* 红底 0x01..0x07 基础设施失败(PCI/MMIO/caps/reset/ring/start)
|
||
* 红底 0x11..0x14 PCI 自检细分(见 xhci_pci_diag_code)
|
||
* 黄底 0x08 所有端口无设备可枚举
|
||
* 黄底 0x0A..0x0F 枚举失败(slot/address/descriptor/configure/scsi)
|
||
* 依赖 dkm_kernel_api.fb_* 字段(32bpp,与 console_fb 一致)。
|
||
* ================================================================ */
|
||
static const u8 g_fb_font_hex[16][8] = {
|
||
{0x3C,0x66,0x6E,0x76,0x66,0x66,0x3C,0x00}, /* 0 */
|
||
{0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00}, /* 1 */
|
||
{0x3C,0x66,0x06,0x0C,0x18,0x30,0x7E,0x00}, /* 2 */
|
||
{0x3C,0x66,0x06,0x1C,0x06,0x66,0x3C,0x00}, /* 3 */
|
||
{0x0C,0x1C,0x3C,0x6C,0x7E,0x0C,0x0C,0x00}, /* 4 */
|
||
{0x7E,0x60,0x7C,0x06,0x06,0x66,0x3C,0x00}, /* 5 */
|
||
{0x1C,0x30,0x60,0x7C,0x66,0x66,0x3C,0x00}, /* 6 */
|
||
{0x7E,0x66,0x06,0x0C,0x18,0x18,0x18,0x00}, /* 7 */
|
||
{0x3C,0x66,0x66,0x3C,0x66,0x66,0x3C,0x00}, /* 8 */
|
||
{0x3C,0x66,0x66,0x3E,0x06,0x0C,0x38,0x00}, /* 9 */
|
||
{0x3C,0x66,0x66,0x7E,0x66,0x66,0x66,0x00}, /* A */
|
||
{0x7C,0x66,0x66,0x7C,0x66,0x66,0x7C,0x00}, /* B */
|
||
{0x3C,0x66,0x60,0x60,0x60,0x66,0x3C,0x00}, /* C */
|
||
{0x78,0x6C,0x66,0x66,0x66,0x6C,0x78,0x00}, /* D */
|
||
{0x7E,0x60,0x60,0x7C,0x60,0x60,0x7E,0x00}, /* E */
|
||
{0x7E,0x60,0x60,0x7C,0x60,0x60,0x60,0x00}, /* F */
|
||
};
|
||
|
||
static void fb_draw_char(u32 *fb, u64 pitch, int x0, int y0, int scale,
|
||
u32 color, int ch) {
|
||
const u8 *g = g_fb_font_hex[ch & 0xF];
|
||
for (int row = 0; row < 8; row++) {
|
||
u8 bits = g[row];
|
||
for (int col = 0; col < 8; col++) {
|
||
if (!(bits & (0x80u >> col))) continue;
|
||
for (int sy = 0; sy < scale; sy++) {
|
||
u32 *line = (u32 *)((u8 *)fb + (u64)(y0 + row * scale + sy) * pitch);
|
||
for (int sx = 0; sx < scale; sx++)
|
||
line[x0 + col * scale + sx] = color;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static void fb_diag_show(const struct dkm_kernel_api *api, u32 bg, u32 code) {
|
||
if (!api || !api->fb_address || !api->fb_width || !api->fb_height)
|
||
return; /* 无 framebuffer 时退化为仅串口日志 */
|
||
u32 *fb = (u32 *)api->fb_address;
|
||
u64 pitch = api->fb_pitch;
|
||
u64 w = api->fb_width, h = api->fb_height;
|
||
for (u64 y = 0; y < h; y++) {
|
||
u32 *line = (u32 *)((u8 *)fb + y * pitch);
|
||
for (u64 x = 0; x < w; x++) line[x] = bg;
|
||
}
|
||
int scale = 8;
|
||
int cw = 8 * scale;
|
||
int cx = (int)(w / 2) - cw; /* 两位数字整体居中 */
|
||
int cy = (int)(h / 2) - (8 * scale) / 2;
|
||
u32 fg = 0xFFFFFFFFu;
|
||
if (code >= 0x10) fb_draw_char(fb, pitch, cx, cy, scale, fg, (int)(code >> 4));
|
||
fb_draw_char(fb, pitch, cx + cw, cy, scale, fg, (int)(code & 0xF));
|
||
/* 停住屏幕,等待实机拍照/观察 */
|
||
for (;;) {
|
||
__asm__ volatile("cli; hlt");
|
||
}
|
||
}
|
||
|
||
/* 任意位数 hex(n=1..8),低位在前填充 */
|
||
static void fb_draw_hexn(u32 *fb, u64 pitch, int x0, int y0, int scale,
|
||
u32 color, u32 v, int n) {
|
||
for (int i = 0; i < n; i++) {
|
||
int nib = (int)((v >> (4 * (n - 1 - i))) & 0xF);
|
||
fb_draw_char(fb, pitch, x0 + i * 8 * scale, y0, scale, color, nib);
|
||
}
|
||
}
|
||
|
||
/* PCI 自检诊断清单项(bus/dev/device_id/class/progif) */
|
||
struct usb_ctl_info {
|
||
u32 bus;
|
||
u32 dev;
|
||
u32 devid; /* PCI device ID */
|
||
u32 cls; /* (class<<8)|subclass */
|
||
u32 progif;
|
||
};
|
||
|
||
/* 扩展诊断:大字标题码 + 下方逐行列出扫描到的 PCI 设备。
|
||
* 每行:bus(1) dev(1) deviceid(4) = 左 6 hex,class(2) progif(2) = 右 4 hex。
|
||
* scale=2 支持 16 行(640x480 完整容纳)。device ID 用于区分芯片型号:
|
||
* Intel 7系: xHCI=1e31, EHCI1=1e2d, EHCI2=1e26
|
||
* Intel 6系: EHCI1=1c2d, EHCI2=1c26 (H61 仅 1 个 EHCI)
|
||
* 第三方 USB3: ASMedia=1b21, Renesas=1033 */
|
||
/* 诊断用,保留 */
|
||
static void fb_diag_show_usb(const struct dkm_kernel_api *api, u32 bg, u32 code,
|
||
const struct usb_ctl_info *usb, u32 usb_count) __attribute__((unused));
|
||
static void fb_diag_show_usb(const struct dkm_kernel_api *api, u32 bg, u32 code,
|
||
const struct usb_ctl_info *usb, u32 usb_count) {
|
||
if (!api || !api->fb_address || !api->fb_width || !api->fb_height)
|
||
return;
|
||
u32 *fb = (u32 *)api->fb_address;
|
||
u64 pitch = api->fb_pitch;
|
||
u64 w = api->fb_width, h = api->fb_height;
|
||
for (u64 y = 0; y < h; y++) {
|
||
u32 *line = (u32 *)((u8 *)fb + y * pitch);
|
||
for (u64 x = 0; x < w; x++) line[x] = bg;
|
||
}
|
||
u32 fg = 0xFFFFFFFFu;
|
||
/* 标题大字(scale 6,两位居中,固定顶部) */
|
||
int s = 6, cw = 8 * s;
|
||
int cx = (int)(w / 2) - cw;
|
||
int cy = 8;
|
||
if (code >= 0x10) fb_draw_char(fb, pitch, cx, cy, s, fg, (int)(code >> 4));
|
||
fb_draw_char(fb, pitch, cx + cw, cy, s, fg, (int)(code & 0xF));
|
||
/* 信息行(scale 2,最多 16 行):
|
||
* 左:bus(1)+dev(1)+devid(4)=6 hex;右:class(2)+progif(2)=4 hex */
|
||
int s2 = 2, cw2 = 8 * s2;
|
||
int row_y = cy + 8 * s + 8;
|
||
int total = (6 + 1 + 4) * cw2; /* 176px */
|
||
int xl = (int)(w / 2) - total / 2;
|
||
int xr = xl + (6 + 1) * cw2;
|
||
u32 max_rows = usb_count < 16 ? usb_count : 16;
|
||
for (u32 i = 0; i < max_rows; i++) {
|
||
u32 bd = ((usb[i].bus & 0xFF) << 4) | (usb[i].dev & 0x1F);
|
||
fb_draw_hexn(fb, pitch, xl, row_y, s2, fg, bd, 2);
|
||
fb_draw_hexn(fb, pitch, xl + 2 * cw2, row_y, s2, fg, usb[i].devid & 0xFFFF, 4);
|
||
fb_draw_hexn(fb, pitch, xr, row_y, s2, fg, usb[i].cls & 0xFFFF, 2);
|
||
fb_draw_hexn(fb, pitch, xr + 2 * cw2, row_y, s2, fg, usb[i].progif & 0xFFFF, 2);
|
||
row_y += 8 * s2 + 4; /* 20px/行 */
|
||
}
|
||
for (;;) {
|
||
__asm__ volatile("cli; hlt");
|
||
}
|
||
}
|
||
|
||
/* 红底 = 基础设施失败,halt(0xFFFF0000 在 BGRA 下是纯红;
|
||
* 原 0xFF0000FF 实际渲染为蓝——0x01 那次实机测试显示“蓝底”即此原因) */
|
||
#define FB_FATAL(api, code) fb_diag_show((api), 0xFFFF0000u, (code))
|
||
/* 红底 PCI 自检:标题码 + USB 控制器清单 */
|
||
#define FB_FATAL_show_usb(api, code) \
|
||
fb_diag_show_usb((api), 0xFFFF0000u, (code), g_diag_usb, g_diag_usb_count)
|
||
/* 黄底 = 枚举失败,halt */
|
||
#define FB_ENUM_FAIL(code) fb_diag_show(g_api, 0xFFFFC800u, (code))
|
||
|
||
static void xhci_zero(void *ptr, u32 len) {
|
||
u8 *p = (u8 *)ptr;
|
||
for (u32 i = 0; i < len; i++) p[i] = 0;
|
||
}
|
||
|
||
static void xhci_memcpy(void *dst, const void *src, u32 len) {
|
||
u8 *d = (u8 *)dst;
|
||
const u8 *s = (const u8 *)src;
|
||
for (u32 i = 0; i < len; i++) d[i] = s[i];
|
||
}
|
||
|
||
/* MMIO read/write */
|
||
static u32 mmio_read32(u32 off) {
|
||
return *(volatile u32 *)(void *)(g_mmio + off);
|
||
}
|
||
|
||
static void mmio_write32(u32 off, u32 value) {
|
||
*(volatile u32 *)(void *)(g_mmio + off) = value;
|
||
}
|
||
|
||
static u64 mmio_read64(u32 off) {
|
||
u32 lo = mmio_read32(off);
|
||
u32 hi = mmio_read32(off + 4);
|
||
return ((u64)hi << 32) | lo;
|
||
}
|
||
|
||
static void mmio_write64(u32 off, u64 value) {
|
||
mmio_write32(off, (u32)(value & 0xffffffffu));
|
||
mmio_write32(off + 4, (u32)(value >> 32));
|
||
}
|
||
|
||
/* Memory barrier (compiler only — x86 TSO guarantees HW ordering) */
|
||
static __inline__ void xhci_mb(void) {
|
||
__asm__ volatile("" ::: "memory");
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 1: PCI discovery + MMIO mapping
|
||
* ================================================================ */
|
||
|
||
/* PCI 自检:区分“传统 0xCF8/0xCFC 配置机制失效” vs “确实无 xHCI 控制器”。
|
||
* 实机现象:xhci_find 全扫描失败(诊断码 0x01)——USB3 主板必然有 xHCI,
|
||
* 扫不到只可能是 (a) 配置机制读不到任何设备 (b) 设备在异常位置 (c) 控制器
|
||
* 是 EHCI 等非 xHCI。返回细分诊断码:
|
||
* 0x11 bus0/dev0/func0 vendor=0xFFFF → 0xCF8/0xCFC 机制失效
|
||
* 0x12 机制可用但全扫描无任何 USB 控制器 (class 0x0C/03)
|
||
* 0x13 有 USB 控制器但无 prog_if=0x30(全部 EHCI/UHCI/OHCI)
|
||
* 0x14 存在 prog_if=0x30 的 xHCI 但 xhci_find 漏匹配(防御性,理论不可能)
|
||
* 同时把扫描到的设备填入 g_diag_usb 供屏幕显示——记录策略:
|
||
* 第一遍收集全部 USB 控制器(class 0C/03,含 bus1+ 第三方芯片),
|
||
* 第二遍收集其他设备(bus0 优先),总上限 16 行。
|
||
* 关键 device ID 判读:Intel 7系 xHCI=1e31 / EHCI1=1e2d / EHCI2=1e26;
|
||
* Intel 6系 EHCI1=1c2d / EHCI2=1c26(H61 仅 1 个);ASMedia=1b21;
|
||
* Renesas=1033。 */
|
||
static struct usb_ctl_info g_diag_usb[16];
|
||
static u32 g_diag_usb_count;
|
||
|
||
#define DIAG_MAX 16
|
||
|
||
static void diag_add(u32 bus, u32 dev, u32 devid, u32 cls, u32 progif) {
|
||
if (g_diag_usb_count >= DIAG_MAX) return;
|
||
g_diag_usb[g_diag_usb_count].bus = bus;
|
||
g_diag_usb[g_diag_usb_count].dev = dev;
|
||
g_diag_usb[g_diag_usb_count].devid = devid;
|
||
g_diag_usb[g_diag_usb_count].cls = cls;
|
||
g_diag_usb[g_diag_usb_count].progif = progif;
|
||
g_diag_usb_count++;
|
||
}
|
||
|
||
/* 诊断用,保留(无 xHCI 场景默认不调用,可通过临时改 driver_init 调试) */
|
||
static u32 xhci_pci_diag_code(void) __attribute__((unused));
|
||
static u32 xhci_pci_diag_code(void) {
|
||
u32 vd0 = dkm_pci_read(0, 0, 0, PCI_VENDOR_ID);
|
||
g_diag_usb_count = 0;
|
||
if ((vd0 & 0xffff) == 0xffff) return 0x11;
|
||
|
||
u32 usb_total = 0, xhci_total = 0;
|
||
|
||
/* 第一遍:USB 控制器(全 bus,含 bus1+ 第三方芯片) */
|
||
for (u16 bus = 0; bus < 256; bus++) {
|
||
for (u8 dev = 0; dev < 32; dev++) {
|
||
u32 vd = dkm_pci_read((u8)bus, dev, 0, PCI_VENDOR_ID);
|
||
if ((vd & 0xffff) == 0xffff) continue;
|
||
u8 header = (u8)(dkm_pci_read((u8)bus, dev, 0, PCI_HEADER) >> 16);
|
||
u8 func_count = (header & 0x80) ? 8 : 1;
|
||
for (u8 func = 0; func < func_count; func++) {
|
||
u32 vd2 = dkm_pci_read((u8)bus, dev, func, PCI_VENDOR_ID);
|
||
if ((vd2 & 0xffff) == 0xffff) continue;
|
||
u32 class_reg = dkm_pci_read((u8)bus, dev, func, 0x08);
|
||
u8 prog_if = (u8)((class_reg >> 8) & 0xff);
|
||
u8 subclass = (u8)((class_reg >> 16) & 0xff);
|
||
u8 class_code = (u8)((class_reg >> 24) & 0xff);
|
||
if (class_code == 0x0C && subclass == 0x03) {
|
||
usb_total++;
|
||
if (prog_if == 0x30) xhci_total++;
|
||
diag_add(bus, dev, vd2 >> 16,
|
||
(u32)((class_code << 8) | subclass), prog_if);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 第二遍:其他设备补位(bus0 优先) */
|
||
if (g_diag_usb_count < DIAG_MAX) {
|
||
for (u16 bus = 0; bus < 256 && g_diag_usb_count < DIAG_MAX; bus++) {
|
||
for (u8 dev = 0; dev < 32 && g_diag_usb_count < DIAG_MAX; dev++) {
|
||
u32 vd = dkm_pci_read((u8)bus, dev, 0, PCI_VENDOR_ID);
|
||
if ((vd & 0xffff) == 0xffff) continue;
|
||
u8 header = (u8)(dkm_pci_read((u8)bus, dev, 0, PCI_HEADER) >> 16);
|
||
u8 func_count = (header & 0x80) ? 8 : 1;
|
||
for (u8 func = 0; func < func_count && g_diag_usb_count < DIAG_MAX; func++) {
|
||
u32 vd2 = dkm_pci_read((u8)bus, dev, func, PCI_VENDOR_ID);
|
||
if ((vd2 & 0xffff) == 0xffff) continue;
|
||
u32 class_reg = dkm_pci_read((u8)bus, dev, func, 0x08);
|
||
u8 prog_if = (u8)((class_reg >> 8) & 0xff);
|
||
u8 subclass = (u8)((class_reg >> 16) & 0xff);
|
||
u8 class_code = (u8)((class_reg >> 24) & 0xff);
|
||
if (class_code == 0x0C && subclass == 0x03) continue; /* 已收集 */
|
||
diag_add(bus, dev, vd2 >> 16,
|
||
(u32)((class_code << 8) | subclass), prog_if);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (usb_total == 0) return 0x12;
|
||
if (xhci_total > 0) return 0x14;
|
||
return 0x13;
|
||
}
|
||
|
||
static int xhci_find_nth(int nth, u8 *out_bus, u8 *out_dev, u8 *out_func) {
|
||
int seen = 0;
|
||
for (u16 bus = 0; bus < 256; bus++) {
|
||
for (u8 dev = 0; dev < 32; dev++) {
|
||
u32 vd = dkm_pci_read((u8)bus, dev, 0, PCI_VENDOR_ID);
|
||
if ((vd & 0xffff) == 0xffff) continue;
|
||
|
||
u8 header = (u8)(dkm_pci_read((u8)bus, dev, 0, PCI_HEADER) >> 16);
|
||
u8 func_count = (header & 0x80) ? 8 : 1;
|
||
for (u8 func = 0; func < func_count; func++) {
|
||
u32 vd2 = dkm_pci_read((u8)bus, dev, func, PCI_VENDOR_ID);
|
||
if ((vd2 & 0xffff) == 0xffff) continue;
|
||
|
||
u32 class_reg = dkm_pci_read((u8)bus, dev, func, 0x08);
|
||
u8 prog_if = (u8)((class_reg >> 8) & 0xff);
|
||
u8 subclass = (u8)((class_reg >> 16) & 0xff);
|
||
u8 class_code = (u8)((class_reg >> 24) & 0xff);
|
||
|
||
if (class_code == XHCI_CLASS_SERIAL &&
|
||
subclass == XHCI_SUBCLASS_USB &&
|
||
prog_if == XHCI_PROGIF_XHCI) {
|
||
if (seen == nth) {
|
||
*out_bus = (u8)bus;
|
||
*out_dev = dev;
|
||
*out_func = func;
|
||
return 0;
|
||
}
|
||
seen++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
static volatile u8 *xhci_map_bar(const struct dkm_kernel_api *api, u64 bar_phys) {
|
||
u64 bar_virt = api->hhdm_offset + bar_phys;
|
||
|
||
/* Prefer mm_map_mmio independent window */
|
||
if (api->mm_map_mmio) {
|
||
void *mapped = api->mm_map_mmio(bar_phys, XHCI_MAP_SIZE);
|
||
if (mapped) {
|
||
g_log->info("[xhci] BAR mapped via mm_map_mmio");
|
||
return (volatile u8 *)mapped;
|
||
}
|
||
g_log->warn("[xhci] mm_map_mmio failed; trying HHDM fallback");
|
||
}
|
||
|
||
/* Check if HHDM covers this BAR */
|
||
if (api->mmio && api->mmio->is_mapped) {
|
||
int level = api->mmio->is_mapped(bar_virt);
|
||
if (level > 0) {
|
||
g_log->info("[xhci] HHDM covers BAR; direct access");
|
||
return (volatile u8 *)(uintptr_t)bar_virt;
|
||
}
|
||
/* Map via page tables */
|
||
if (api->mmio->map_mmio) {
|
||
int rc = api->mmio->map_mmio(bar_phys, XHCI_MAP_SIZE);
|
||
if (rc == 0 && api->mmio->is_mapped(bar_virt) > 0) {
|
||
g_log->info("[xhci] BAR mapped via page tables");
|
||
return (volatile u8 *)(uintptr_t)bar_virt;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Low BAR fallback */
|
||
if (bar_phys < 0x100000000ULL) {
|
||
g_log->warn("[xhci] assuming HHDM covers low BAR");
|
||
return (volatile u8 *)(uintptr_t)bar_virt;
|
||
}
|
||
|
||
g_log->error("[xhci] BAR MMIO unavailable");
|
||
return (volatile u8 *)0;
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 1: BIOS Handoff via Extended Capabilities
|
||
* ================================================================ */
|
||
|
||
static void xhci_bios_handoff(void) {
|
||
u32 hcc1 = mmio_read32(XHCI_CAP_HCCPARAMS1);
|
||
u32 ext_ptr = (hcc1 >> 16) & 0xFFFF;
|
||
if (!ext_ptr) return;
|
||
|
||
/* Walk extended capabilities (offsets in 32-bit words from MMIO base) */
|
||
u32 off = ext_ptr;
|
||
for (int i = 0; i < 16 && off != 0; i++) {
|
||
u32 dw0 = mmio_read32(off * 4);
|
||
u8 id = (u8)(dw0 & 0xFF);
|
||
u8 next = (u8)((dw0 >> 8) & 0xFF);
|
||
|
||
if (id == 1) {
|
||
/* USB Legacy Support Capability */
|
||
u32 legsup = mmio_read32(off * 4 + 4);
|
||
log_hex("[xhci] USBLEGSUP=", legsup);
|
||
|
||
if (legsup & 0x0000FFFFu) {
|
||
/* BIOS owns controller — request ownership */
|
||
mmio_write32(off * 4 + 4, legsup | (1u << 16));
|
||
u64 deadline = dkm_rdtsc() + dkm_tsc_per_ms * 1000;
|
||
while (dkm_rdtsc() < deadline) {
|
||
u32 cur = mmio_read32(off * 4 + 4);
|
||
if (!(cur & 0x0000FFFFu)) {
|
||
g_log->info("[xhci] BIOS handoff complete");
|
||
/* Clear SMI triggers */
|
||
mmio_write32(off * 4 + 8, 0);
|
||
break;
|
||
}
|
||
__asm__ volatile("pause");
|
||
}
|
||
}
|
||
}
|
||
|
||
off = next ? (off + next) : 0;
|
||
}
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 1: Controller reset and init
|
||
* ================================================================ */
|
||
|
||
static int xhci_controller_reset(void) {
|
||
u32 usbcmd = mmio_read32(g_cap_length + XHCI_OP_USBCMD);
|
||
u32 usbsts = mmio_read32(g_cap_length + XHCI_OP_USBSTS);
|
||
|
||
/* If controller is running, halt it first */
|
||
if (!(usbsts & USBSTS_HCH)) {
|
||
mmio_write32(g_cap_length + XHCI_OP_USBCMD, usbcmd & ~USBCMD_RS);
|
||
u64 deadline = dkm_rdtsc() + dkm_tsc_per_ms * 1000;
|
||
while (dkm_rdtsc() < deadline) {
|
||
if (mmio_read32(g_cap_length + XHCI_OP_USBSTS) & USBSTS_HCH) break;
|
||
__asm__ volatile("pause");
|
||
}
|
||
}
|
||
|
||
/* Wait for CNR to clear */
|
||
{
|
||
u64 deadline = dkm_rdtsc() + dkm_tsc_per_ms * 1000;
|
||
while (dkm_rdtsc() < deadline) {
|
||
if (!(mmio_read32(g_cap_length + XHCI_OP_USBSTS) & USBSTS_CNR)) break;
|
||
__asm__ volatile("pause");
|
||
}
|
||
}
|
||
|
||
/* Assert HC reset */
|
||
usbcmd = mmio_read32(g_cap_length + XHCI_OP_USBCMD);
|
||
mmio_write32(g_cap_length + XHCI_OP_USBCMD, usbcmd | USBCMD_RST);
|
||
|
||
/* Wait for reset to complete (RST bit clears) */
|
||
{
|
||
u64 deadline = dkm_rdtsc() + dkm_tsc_per_ms * 2000;
|
||
while (dkm_rdtsc() < deadline) {
|
||
usbcmd = mmio_read32(g_cap_length + XHCI_OP_USBCMD);
|
||
if (!(usbcmd & USBCMD_RST)) {
|
||
g_log->info("[xhci] controller reset complete");
|
||
return 0;
|
||
}
|
||
__asm__ volatile("pause");
|
||
}
|
||
}
|
||
g_log->error("[xhci] controller reset timeout");
|
||
return -1;
|
||
}
|
||
|
||
static int xhci_parse_caps(void) {
|
||
/* Read CAPLENGTH (low byte of MMIO+0) */
|
||
g_cap_length = mmio_read32(0) & 0xFF;
|
||
|
||
u32 hcs1 = mmio_read32(XHCI_CAP_HCSPARAMS1);
|
||
u32 hcs2 = mmio_read32(XHCI_CAP_HCSPARAMS2);
|
||
u32 hcc1 = mmio_read32(XHCI_CAP_HCCPARAMS1);
|
||
|
||
g_max_slots = hcs1 & 0xFF;
|
||
g_max_intrs = (hcs1 >> 8) & 0x7FF;
|
||
g_max_ports = (hcs1 >> 24) & 0xFF;
|
||
|
||
/* Context size: CSZ bit4 of HCCPARAMS1 */
|
||
g_ctx_size = (hcc1 & (1u << 4)) ? 64 : 32;
|
||
|
||
/* Per-Port Power Control: PPC bit3 of HCCPARAMS1 */
|
||
g_ppc = (hcc1 >> 3) & 1;
|
||
|
||
/* Scratchpad buffer count from HCSPARAMS2 */
|
||
g_spb_count = ((hcs2 >> 21) & 0x1F) | ((hcs2 >> 27) & 0x1F);
|
||
|
||
/*
|
||
* Port Register Set offset: absolute from MMIO base.
|
||
* xHCI spec: "Port Register Sets are located at offset 0x400
|
||
* from the beginning of the Operational Register Space."
|
||
* Operational Register Space starts at offset CAPLENGTH.
|
||
* Therefore absolute port offset = CAPLENGTH + 0x400.
|
||
* Verified against QEMU source (XHCI_REGS_OFFSET_PORT =
|
||
* XHCI_REGS_OFFSET_OPER + XHCI_REGS_LENGTH_OPER) and Linux
|
||
* kernel (op_regs + PORTSC_OFFSET where op_regs = base + cap_length).
|
||
*/
|
||
g_port_base = g_cap_length + XHCI_PORT_BASE_OFF;
|
||
|
||
/* Doorbell and Runtime offsets — full 32-bit, clear reserved low bits */
|
||
g_doorbell_off = mmio_read32(XHCI_CAP_DBOFF) & 0xFFFFFFF0u;
|
||
g_runtime_off = mmio_read32(XHCI_CAP_RTSOFF) & 0xFFFFFFE0u;
|
||
|
||
/* Page size: PAGESIZE 寄存器 bit[n] 置位表示支持 2^(n+12) 字节页。
|
||
* 取置位的最低 bit(xHCI 规范要求软件使用最小支持页)。
|
||
* 原实现 (ps_reg & 0xF) 语义错误,实机多 bit 置位时会得到错误页大小。 */
|
||
u32 ps_reg = mmio_read32(g_cap_length + XHCI_OP_PAGESIZE);
|
||
u32 ps_bits = ps_reg & 0xFFFFu;
|
||
u32 ps_shift = 0;
|
||
if (ps_bits) {
|
||
while (!(ps_bits & 1u)) { ps_bits >>= 1; ps_shift++; }
|
||
g_page_size = 4096u << ps_shift;
|
||
} else {
|
||
g_page_size = 4096;
|
||
}
|
||
|
||
log_hex("[xhci] CAPLEN=", g_cap_length);
|
||
log_hex("[xhci] MaxSlots=", g_max_slots);
|
||
log_hex("[xhci] MaxPorts=", g_max_ports);
|
||
log_hex("[xhci] MaxIntrs=", g_max_intrs);
|
||
log_hex("[xhci] ContextSize=", g_ctx_size);
|
||
log_hex("[xhci] PPC=", g_ppc);
|
||
log_hex("[xhci] PortBaseOff=", g_port_base);
|
||
log_hex("[xhci] ScratchpadBufs=", g_spb_count);
|
||
log_hex("[xhci] DBOFF=", g_doorbell_off);
|
||
log_hex("[xhci] RTSOFF=", g_runtime_off);
|
||
log_hex("[xhci] PageSize=", g_page_size);
|
||
|
||
if (g_max_slots == 0 || g_max_ports == 0) {
|
||
g_log->error("[xhci] invalid capability parameters");
|
||
return -1;
|
||
}
|
||
if (g_max_intrs == 0) {
|
||
g_log->warn("[xhci] MaxIntrs=0 (no interrupt register sets); polling mode only");
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 2: TRB Ring management
|
||
* ================================================================ */
|
||
|
||
static int xhci_ring_init(xhci_ring *ring, u32 size) {
|
||
if (!g_api->dma) return -1;
|
||
/* Allocate one page (must be 4K aligned for DMA) */
|
||
if (g_api->dma->alloc_pages(1, 4096, 0x100000000ULL, &ring->dma) != 0)
|
||
return -2;
|
||
|
||
xhci_zero(ring->dma.virt, ring->dma.size);
|
||
ring->size = size;
|
||
ring->enqueue = 0;
|
||
ring->ccs = 1; /* Producer Cycle State starts at 1 */
|
||
|
||
/* Append Link TRB at end of ring */
|
||
xhci_trb *trbs = (xhci_trb *)ring->dma.virt;
|
||
u32 link_idx = size - 1;
|
||
trbs[link_idx].parameter = ring->dma.phys;
|
||
trbs[link_idx].status = 0;
|
||
trbs[link_idx].control = (TRB_TYPE_LINK << TRB_TYPE_SHIFT) | TRB_TC | TRB_CYCLE;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Enqueue a TRB onto a ring and return the enqueue index before advance */
|
||
static u32 xhci_ring_enqueue(xhci_ring *ring, u64 param, u32 status, u32 control) {
|
||
xhci_trb *trbs = (xhci_trb *)ring->dma.virt;
|
||
u32 idx = ring->enqueue;
|
||
|
||
/* Set cycle bit in control */
|
||
control &= ~TRB_CYCLE;
|
||
if (ring->ccs) control |= TRB_CYCLE;
|
||
|
||
trbs[idx].parameter = param;
|
||
trbs[idx].status = status;
|
||
trbs[idx].control = control;
|
||
|
||
/* Advance enqueue */
|
||
ring->enqueue = (idx + 1) % (ring->size - 1); /* -1 to skip Link TRB */
|
||
/* If we wrapped, toggle CCS */
|
||
if (idx + 1 >= ring->size - 1)
|
||
ring->ccs ^= 1;
|
||
|
||
return idx;
|
||
}
|
||
|
||
/* Ring a doorbell: slot_id=0 for command, otherwise endpoint doorbell。
|
||
* Doorbell Registers 每个 4 字节(xHCI 1.2 §5.6):offset = slot_id * 4。
|
||
* BUG-FIX: 原实现 slot_id * 0x20 偏移错误——QEMU 门铃区 reg>>=2 后
|
||
* reg 即 slot_id,0x20 偏移会敲到 slot 8,该 slot 无 EP → 控制器不
|
||
* 处理 TRB → 无 Transfer Event → 轮询超时。 */
|
||
static void xhci_ring_doorbell(u32 slot_id, u32 target) {
|
||
mmio_write32(g_doorbell_off + slot_id * 4, target);
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 2: Event Ring polling
|
||
* ================================================================ */
|
||
|
||
/* Poll event ring for a specific TRB type with TSC timeout.
|
||
* Returns 0 on success, fills out_param/out_status/out_control.
|
||
* Returns -1 on timeout, -2 on error completion code. */
|
||
static int xhci_poll_event(u32 expected_type, u64 timeout_ms,
|
||
u64 *out_param, u32 *out_status, u32 *out_control) {
|
||
xhci_trb *trbs = (xhci_trb *)g_evt_ring.dma.virt;
|
||
u64 deadline = dkm_rdtsc() + dkm_tsc_per_ms * timeout_ms;
|
||
|
||
for (;;) {
|
||
xhci_trb *evt = &trbs[g_evt_ring.enqueue];
|
||
u32 ctrl = evt->control;
|
||
u8 cycle = (u8)(ctrl & TRB_CYCLE);
|
||
|
||
/* Check if this TRB is owned by us (consumer) */
|
||
if (cycle == (u8)g_evt_ccs) {
|
||
u8 type = (u8)((ctrl >> TRB_TYPE_SHIFT) & TRB_TYPE_MASK);
|
||
|
||
/* Process this event */
|
||
if (type == expected_type) {
|
||
if (out_param) *out_param = evt->parameter;
|
||
if (out_status) *out_status = evt->status;
|
||
if (out_control)*out_control= evt->control;
|
||
|
||
/* Advance dequeue */
|
||
g_evt_ring.enqueue = (g_evt_ring.enqueue + 1) % (g_evt_ring.size - 1);
|
||
if (g_evt_ring.enqueue == 0) g_evt_ccs ^= 1;
|
||
|
||
/* Update ERDP: 地址 16 字节对齐,bit0 保持 0,bit3=EHB 清挂起 */
|
||
u64 erdp = g_evt_ring.dma.phys + (u64)g_evt_ring.enqueue * sizeof(xhci_trb);
|
||
mmio_write64(g_runtime_off + XHCI_RT_IR0_ERDP_LO, erdp | ERDP_EHB);
|
||
|
||
/* Check completion code */
|
||
u32 cc = (evt->status >> 24) & 0xFF;
|
||
if (cc != 1) { /* 1 = Success */
|
||
if (cc != 0) {
|
||
log_hex("[xhci] event completion code=", cc);
|
||
return -2;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Unexpected event type — handle or skip */
|
||
if (type == EVT_TYPE_PORT_CHANGE) {
|
||
/* Port Status Change Event: clear PORTSC change bits */
|
||
u32 port_id = (u32)((evt->parameter >> 24) & 0xFF);
|
||
if (port_id > 0 && port_id <= g_max_ports) {
|
||
u32 port_off = g_port_base + (port_id - 1) * XHCI_PORT_STRIDE + XHCI_PORT_SC;
|
||
/* Read PORTSC and write back to clear change bits (W1C) */
|
||
u32 portsc = mmio_read32(port_off);
|
||
mmio_write32(port_off, portsc);
|
||
log_hex("[xhci] port change event, port=", port_id);
|
||
}
|
||
} else {
|
||
log_hex("[xhci] unexpected event type=", type);
|
||
}
|
||
g_evt_ring.enqueue = (g_evt_ring.enqueue + 1) % (g_evt_ring.size - 1);
|
||
if (g_evt_ring.enqueue == 0) g_evt_ccs ^= 1;
|
||
u64 erdp2 = g_evt_ring.dma.phys + (u64)g_evt_ring.enqueue * sizeof(xhci_trb);
|
||
mmio_write64(g_runtime_off + XHCI_RT_IR0_ERDP_LO, erdp2 | ERDP_EHB);
|
||
continue;
|
||
}
|
||
|
||
if (dkm_rdtsc() > deadline) {
|
||
g_log->warn("[xhci] event poll timeout");
|
||
return -1;
|
||
}
|
||
__asm__ volatile("pause");
|
||
}
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 2: Command submission
|
||
* ================================================================ */
|
||
|
||
static int xhci_cmd_submit(u64 param, u32 status, u32 control,
|
||
u64 timeout_ms,
|
||
u64 *out_param, u32 *out_status, u32 *out_control) {
|
||
/* Clear Event Ring interrupt pending */
|
||
mmio_write32(g_runtime_off + XHCI_RT_IR0_IMAN,
|
||
mmio_read32(g_runtime_off + XHCI_RT_IR0_IMAN) | 2u);
|
||
|
||
/* Enqueue command TRB */
|
||
xhci_ring_enqueue(&g_cmd_ring, param, status, control);
|
||
xhci_mb();
|
||
|
||
/* Debug: verify CRCR and command ring state */
|
||
log_hex("[xhci] CRCR=", mmio_read64(g_cap_length + XHCI_OP_CRCR_LO));
|
||
log_hex("[xhci] cmd ring enqueue=", g_cmd_ring.enqueue);
|
||
log_hex("[xhci] cmd ring ccs=", g_cmd_ring.ccs);
|
||
|
||
xhci_ring_doorbell(0, 0); /* Command ring doorbell */
|
||
|
||
/* Debug: check USBSTS after doorbell */
|
||
log_hex("[xhci] USBSTS after db=", mmio_read32(g_cap_length + XHCI_OP_USBSTS));
|
||
|
||
/* Poll for Command Completion Event */
|
||
/* Debug: dump event ring head entry */
|
||
{
|
||
xhci_trb *evts = (xhci_trb *)g_evt_ring.dma.virt;
|
||
log_hex("[xhci] evt ring head=", g_evt_ring.enqueue);
|
||
log_hex("[xhci] evt[0] ctrl=", evts[g_evt_ring.enqueue].control);
|
||
}
|
||
|
||
int rc = xhci_poll_event(EVT_TYPE_CMD_COMPLETE, timeout_ms,
|
||
out_param, out_status, out_control);
|
||
|
||
/* Clear EINT */
|
||
mmio_write32(g_cap_length + XHCI_OP_USBSTS,
|
||
mmio_read32(g_cap_length + XHCI_OP_USBSTS) | USBSTS_EINT);
|
||
|
||
return rc;
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 2: Setup controller rings + DCBAA + Scratchpad
|
||
* ================================================================ */
|
||
|
||
static int xhci_setup_rings(void) {
|
||
if (!g_api->dma) {
|
||
g_log->warn("[xhci] DMA API unavailable");
|
||
return -1;
|
||
}
|
||
|
||
/* Command Ring */
|
||
if (xhci_ring_init(&g_cmd_ring, CMD_RING_SIZE) != 0) {
|
||
g_log->error("[xhci] cmd ring alloc failed");
|
||
return -2;
|
||
}
|
||
log_hex("[xhci] cmd ring phys=", g_cmd_ring.dma.phys);
|
||
|
||
/* Event Ring */
|
||
if (xhci_ring_init(&g_evt_ring, EVT_RING_SIZE) != 0) {
|
||
g_log->error("[xhci] evt ring alloc failed");
|
||
return -3;
|
||
}
|
||
g_evt_ccs = 1; /* Consumer Cycle State starts at 1 */
|
||
log_hex("[xhci] evt ring phys=", g_evt_ring.dma.phys);
|
||
|
||
/* ERST (Event Ring Segment Table) */
|
||
if (g_api->dma->alloc_pages(1, 4096, 0x100000000ULL, &g_erst) != 0) {
|
||
g_log->error("[xhci] ERST alloc failed");
|
||
return -4;
|
||
}
|
||
xhci_erst_entry *erst = (xhci_erst_entry *)g_erst.virt;
|
||
xhci_zero(erst, sizeof(xhci_erst_entry));
|
||
erst->seg_addr = g_evt_ring.dma.phys;
|
||
erst->seg_size = EVT_RING_SIZE;
|
||
|
||
/* DCBAA (Device Context Base Address Array) */
|
||
u32 dcbaa_size = (g_max_slots + 1) * 8;
|
||
u32 dcbaa_pages = (dcbaa_size + 4095) / 4096;
|
||
if (dcbaa_pages == 0) dcbaa_pages = 1;
|
||
if (g_api->dma->alloc_pages(dcbaa_pages, 4096, 0x100000000ULL, &g_dcbaa) != 0) {
|
||
g_log->error("[xhci] DCBAA alloc failed");
|
||
return -5;
|
||
}
|
||
xhci_zero(g_dcbaa.virt, g_dcbaa.size);
|
||
log_hex("[xhci] DCBAA phys=", g_dcbaa.phys);
|
||
|
||
/* Scratchpad Buffer Array (if needed) */
|
||
if (g_spb_count > 0) {
|
||
u32 sp_arr_size = g_spb_count * 8;
|
||
u32 sp_arr_pages = (sp_arr_size + 4095) / 4096;
|
||
if (sp_arr_pages == 0) sp_arr_pages = 1;
|
||
if (g_api->dma->alloc_pages(sp_arr_pages, 4096, 0x100000000ULL, &g_sp_bufs) != 0) {
|
||
g_log->error("[xhci] scratchpad array alloc failed");
|
||
return -6;
|
||
}
|
||
xhci_zero(g_sp_bufs.virt, g_sp_bufs.size);
|
||
|
||
/* Allocate individual scratchpad buffer pages */
|
||
u32 sp_count = g_spb_count;
|
||
if (sp_count > 32) sp_count = 32; /* safety cap */
|
||
for (u32 i = 0; i < sp_count; i++) {
|
||
if (g_api->dma->alloc_pages(1, 4096, 0x100000000ULL, &g_sp_pages[i]) != 0) {
|
||
g_log->error("[xhci] scratchpad page alloc failed");
|
||
return -7;
|
||
}
|
||
xhci_zero(g_sp_pages[i].virt, g_sp_pages[i].size);
|
||
/* Fill scratchpad buffer array with physical addresses */
|
||
u64 *sp_arr = (u64 *)g_sp_bufs.virt;
|
||
sp_arr[i] = g_sp_pages[i].phys;
|
||
}
|
||
|
||
/* DCBAA slot 0 points to scratchpad buffer array */
|
||
u64 *dcbaa_entries = (u64 *)g_dcbaa.virt;
|
||
dcbaa_entries[0] = g_sp_bufs.phys;
|
||
log_hex("[xhci] SPB array phys=", g_sp_bufs.phys);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int xhci_controller_start(void) {
|
||
u32 cap_off = g_cap_length;
|
||
|
||
/* Configure Command Ring */
|
||
u64 crcr = g_cmd_ring.dma.phys | (u64)g_cmd_ring.ccs;
|
||
mmio_write64(cap_off + XHCI_OP_CRCR_LO, crcr);
|
||
|
||
/* Configure DCBAAP */
|
||
mmio_write64(cap_off + XHCI_OP_DCBAAP_LO, g_dcbaa.phys);
|
||
|
||
/* Configure MaxSlotsEn */
|
||
mmio_write32(cap_off + XHCI_OP_CONFIG, g_max_slots);
|
||
|
||
/* Configure Event Ring */
|
||
mmio_write32(g_runtime_off + XHCI_RT_IR0_ERSTSZ, 1);
|
||
mmio_write64(g_runtime_off + XHCI_RT_IR0_ERSTBA_LO, g_erst.phys);
|
||
u64 erdp = g_evt_ring.dma.phys;
|
||
mmio_write64(g_runtime_off + XHCI_RT_IR0_ERDP_LO, erdp | ERDP_EHB);
|
||
|
||
/* Enable interrupt (IMAN.IE=1) */
|
||
mmio_write32(g_runtime_off + XHCI_RT_IR0_IMAN,
|
||
mmio_read32(g_runtime_off + XHCI_RT_IR0_IMAN) | (1u << 1));
|
||
|
||
/* Enable controller interrupts + start controller */
|
||
u32 usbcmd = mmio_read32(cap_off + XHCI_OP_USBCMD);
|
||
usbcmd |= USBCMD_INTE | USBCMD_RS;
|
||
mmio_write32(cap_off + XHCI_OP_USBCMD, usbcmd);
|
||
|
||
/* Wait for HCH to clear (controller running) */
|
||
u64 deadline = dkm_rdtsc() + dkm_tsc_per_ms * 2000;
|
||
while (dkm_rdtsc() < deadline) {
|
||
u32 usbsts = mmio_read32(cap_off + XHCI_OP_USBSTS);
|
||
if (!(usbsts & USBSTS_HCH)) {
|
||
g_log->info("[xhci] controller started");
|
||
return 0;
|
||
}
|
||
__asm__ volatile("pause");
|
||
}
|
||
g_log->error("[xhci] controller start timeout");
|
||
return -1;
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 3: Port detection and reset
|
||
* ================================================================ */
|
||
|
||
static int xhci_reset_port(u32 port_idx) {
|
||
u32 port_off = g_port_base + port_idx * XHCI_PORT_STRIDE + XHCI_PORT_SC;
|
||
u32 portsc = mmio_read32(port_off);
|
||
|
||
/* Check if device is connected */
|
||
if (!(portsc & PORTSC_CCS)) return -1;
|
||
|
||
/* Already enabled? */
|
||
if (portsc & PORTSC_PED) return 0;
|
||
|
||
/* 只置 PR,并清掉 RW1C 位。把读到的 PORTSC 原样或上 PR 会把
|
||
* PED=0 和已经为 1 的变化位写回去,实机上复位经常起不来。 */
|
||
u32 cmd = portsc & ~((1u << 1) | PORTSC_PR | 0x00FE0000u);
|
||
cmd |= PORTSC_PR;
|
||
if (portsc & (1u << 9)) cmd |= (1u << 9);
|
||
mmio_write32(port_off, cmd);
|
||
|
||
/* Wait for PED (Port Enabled) with 500ms timeout */
|
||
u64 deadline = dkm_rdtsc() + dkm_tsc_per_ms * 500;
|
||
while (dkm_rdtsc() < deadline) {
|
||
portsc = mmio_read32(port_off);
|
||
if (portsc & PORTSC_PED) {
|
||
/* Clear port change bits (W1C: write 1 to clear) */
|
||
mmio_write32(port_off, portsc);
|
||
DKM_STAT_INC(xhci_port_reset_ok);
|
||
|
||
/* Drain any Port Status Change Events from the event ring.
|
||
* Port reset generates a Port Status Change Event that must
|
||
* be consumed before submitting new commands, otherwise the
|
||
* event ring gets congested and command completion events
|
||
* may not be delivered. */
|
||
dkm_delay_ms(10); /* Allow controller to post the event */
|
||
xhci_trb *evts = (xhci_trb *)g_evt_ring.dma.virt;
|
||
for (int drain = 0; drain < 16; drain++) {
|
||
xhci_trb *evt = &evts[g_evt_ring.enqueue];
|
||
u32 ctrl = evt->control;
|
||
u8 cycle = (u8)(ctrl & TRB_CYCLE);
|
||
if (cycle != (u8)g_evt_ccs) break; /* No more events */
|
||
u8 type = (u8)((ctrl >> TRB_TYPE_SHIFT) & TRB_TYPE_MASK);
|
||
if (type == EVT_TYPE_PORT_CHANGE) {
|
||
u32 port_id = (u32)((evt->parameter >> 24) & 0xFF);
|
||
log_hex("[xhci] drained port change event, port=", port_id);
|
||
}
|
||
/* Advance dequeue */
|
||
g_evt_ring.enqueue = (g_evt_ring.enqueue + 1) % (g_evt_ring.size - 1);
|
||
if (g_evt_ring.enqueue == 0) g_evt_ccs ^= 1;
|
||
u64 erdp = g_evt_ring.dma.phys + (u64)g_evt_ring.enqueue * sizeof(xhci_trb);
|
||
mmio_write64(g_runtime_off + XHCI_RT_IR0_ERDP_LO, erdp | ERDP_EHB);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
/* Check if device disconnected during reset */
|
||
if (!(portsc & PORTSC_CCS)) return -2;
|
||
__asm__ volatile("pause");
|
||
}
|
||
g_log->warn("[xhci] port reset timeout");
|
||
return -3;
|
||
}
|
||
|
||
static u8 xhci_get_port_speed(u32 port_idx) {
|
||
u32 port_off = g_port_base + port_idx * XHCI_PORT_STRIDE + XHCI_PORT_SC;
|
||
u32 portsc = mmio_read32(port_off);
|
||
return (u8)((portsc >> PORTSC_SPEED_SHIFT) & PORTSC_SPEED_MASK);
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 3: Enable Slot
|
||
* ================================================================ */
|
||
|
||
static int xhci_enable_slot(u8 *out_slot_id) {
|
||
/* Submit Enable Slot Command TRB */
|
||
u32 control = (TRB_TYPE_ENABLE_SLOT << TRB_TYPE_SHIFT);
|
||
u64 out_param = 0;
|
||
u32 out_status = 0;
|
||
u32 out_control = 0;
|
||
|
||
int rc = xhci_cmd_submit(0, 0, control, 5000, &out_param, &out_status, &out_control);
|
||
if (rc != 0) {
|
||
g_log->error("[xhci] Enable Slot failed");
|
||
return rc;
|
||
}
|
||
|
||
/* Slot ID 位于 Command Completion Event 的 TRB control[31:24]。
|
||
* (xHCI 1.2 §6.4.2.2:CMD_COMPLETE 事件 TRB parameter = 命令 TRB 地址,
|
||
* slot id 在 control 高字节 —— QEMU hcd-xhci.c xhci_write_event 佐证) */
|
||
*out_slot_id = (u8)((out_control >> 24) & 0xFF);
|
||
log_hex("[xhci] slot_id=", *out_slot_id);
|
||
return 0;
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 3: Address Device
|
||
* ================================================================ */
|
||
|
||
static int xhci_address_device(u8 slot_id, int bsr) {
|
||
/* Input Context physical address */
|
||
u64 input_ctx_phys = g_input_ctx.phys;
|
||
|
||
u32 control = (TRB_TYPE_ADDRESS_DEV << TRB_TYPE_SHIFT);
|
||
if (bsr) control |= (1u << 9); /* BSR (Block Set Address) */
|
||
|
||
/* Slot ID goes in control bits [31:24] */
|
||
control |= ((u32)slot_id << 24);
|
||
|
||
u64 out_param = 0;
|
||
u32 out_status = 0;
|
||
u32 out_control = 0;
|
||
int rc = xhci_cmd_submit(input_ctx_phys, 0, control, 5000, &out_param, &out_status, &out_control);
|
||
if (rc != 0) {
|
||
g_log->error("[xhci] Address Device failed");
|
||
return rc;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 3: Configure Endpoint
|
||
* ================================================================ */
|
||
|
||
static int xhci_configure_endpoint(u8 slot_id) {
|
||
u64 input_ctx_phys = g_input_ctx.phys;
|
||
u32 control = (TRB_TYPE_CONFIGURE_EP << TRB_TYPE_SHIFT) | ((u32)slot_id << 24);
|
||
|
||
u64 out_param = 0;
|
||
u32 out_status = 0;
|
||
u32 out_control = 0;
|
||
int rc = xhci_cmd_submit(input_ctx_phys, 0, control, 5000, &out_param, &out_status, &out_control);
|
||
if (rc != 0) {
|
||
g_log->error("[xhci] Configure Endpoint failed");
|
||
return rc;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 3: Control Transfer (EP0)
|
||
* ================================================================ */
|
||
|
||
static int xhci_control_transfer(const u8 *setup_pkt, u8 *data, u32 data_len,
|
||
u8 direction) {
|
||
/* Setup Stage TRB */
|
||
u64 setup_val = ((u64)setup_pkt[4] << 32) | ((u64)setup_pkt[5] << 40) |
|
||
((u64)setup_pkt[6] << 48) | ((u64)setup_pkt[7] << 56);
|
||
setup_val |= (u64)setup_pkt[0] | ((u64)setup_pkt[1] << 8) |
|
||
((u64)setup_pkt[2] << 16) | ((u64)setup_pkt[3] << 24);
|
||
|
||
u32 setup_status = 8; /* Setup packet length */
|
||
/* Setup Stage TRB(不设 IOC:控制传输只需在最后一个 TRB 上报完成,
|
||
* 中间 TRB 设 IOC 会让 QEMU/实机产生多余 Transfer Event,残留事件
|
||
* 污染后续 CMD_COMPLETE 轮询——日志表现为 "unexpected event type") */
|
||
u32 setup_control = (TRB_TYPE_SETUP_STAGE << TRB_TYPE_SHIFT);
|
||
/* Setup Stage TRB 必须设置 IDT (Immediate Data, bit6):
|
||
* setup 包 8 字节直接内嵌在 TRB parameter 字段。
|
||
* BUG-FIX: 原实现缺 IDT,QEMU xhci_fire_ctl_transfer 校验
|
||
* `!(trb_setup->control & TRB_TR_IDT)` 直接返回 -1 → 无 Transfer
|
||
* Event → 轮询超时(GET_DESCRIPTOR 失败)。 */
|
||
setup_control |= (1u << 6); /* TRB_TR_IDT */
|
||
/* Set direction in TRB: bit 16 of Setup Stage TRB */
|
||
if (direction) setup_control |= (1u << 16);
|
||
|
||
xhci_ring_enqueue(&g_ep0_ring, setup_val, setup_status, setup_control);
|
||
|
||
/* Data Stage TRB (if data_len > 0) */
|
||
if (data_len > 0 && data) {
|
||
/* Copy data to transfer buffer for OUT direction */
|
||
if (direction == 0) {
|
||
xhci_memcpy(g_xfer_buf.virt, data, data_len);
|
||
__asm__ volatile("wbinvd" ::: "memory");
|
||
}
|
||
|
||
u64 data_phys = direction ? g_xfer_buf.phys : g_xfer_buf.phys;
|
||
u32 data_status = data_len;
|
||
/* Data Stage 不设 IOC(避免多余事件残留) */
|
||
u32 data_control = (TRB_TYPE_DATA_STAGE << TRB_TYPE_SHIFT);
|
||
if (direction) data_control |= (1u << 16); /* DIR=IN */
|
||
|
||
xhci_ring_enqueue(&g_ep0_ring, data_phys, data_status, data_control);
|
||
}
|
||
|
||
/* Status Stage TRB */
|
||
u32 status_dir = direction ? 0 : 1; /* opposite of data direction */
|
||
u32 status_control = (TRB_TYPE_STATUS_STAGE << TRB_TYPE_SHIFT) | TRB_IOC;
|
||
if (status_dir) status_control |= (1u << 16);
|
||
|
||
xhci_ring_enqueue(&g_ep0_ring, 0, 0, status_control);
|
||
|
||
xhci_mb();
|
||
xhci_ring_doorbell(g_msc.slot_id, 1); /* EP1 = EP0+1 */
|
||
|
||
/* Poll for Transfer Event */
|
||
u64 timeout_ms = 5000;
|
||
u64 out_param = 0;
|
||
u32 out_status = 0;
|
||
int rc = xhci_poll_event(EVT_TYPE_TRANSFER, timeout_ms,
|
||
&out_param, &out_status, 0);
|
||
if (rc != 0) {
|
||
g_log->warn("[xhci] control transfer failed");
|
||
return rc;
|
||
}
|
||
|
||
/* For IN direction, copy data from DMA buffer */
|
||
if (direction && data_len > 0 && data) {
|
||
xhci_memcpy(data, g_xfer_buf.virt, data_len);
|
||
}
|
||
|
||
/* Clear EINT */
|
||
mmio_write32(g_cap_length + XHCI_OP_USBSTS,
|
||
mmio_read32(g_cap_length + XHCI_OP_USBSTS) | USBSTS_EINT);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 3: USB Descriptor reads
|
||
* ================================================================ */
|
||
|
||
static int xhci_get_device_descriptor(u8 *out_desc) {
|
||
u8 setup[8];
|
||
setup[0] = 0x80; /* bmRequestType: Device-to-Host, Standard, Device */
|
||
setup[1] = 0x06; /* bRequest: GET_DESCRIPTOR */
|
||
setup[2] = 0x00; /* wValue low: Descriptor Index */
|
||
setup[3] = 0x01; /* wValue high: Device Descriptor */
|
||
setup[4] = 0x00; /* wIndex low */
|
||
setup[5] = 0x00; /* wIndex high */
|
||
setup[6] = 0x12; /* wLength low: 18 bytes */
|
||
setup[7] = 0x00; /* wLength high */
|
||
|
||
xhci_zero(out_desc, 18);
|
||
return xhci_control_transfer(setup, out_desc, 18, 1);
|
||
}
|
||
|
||
static int xhci_get_config_descriptor(u8 *out_desc, u32 max_len) {
|
||
u8 setup[8];
|
||
setup[0] = 0x80; /* Device-to-Host, Standard, Device */
|
||
setup[1] = 0x06; /* GET_DESCRIPTOR */
|
||
setup[2] = 0x00; /* Index */
|
||
setup[3] = 0x02; /* Configuration Descriptor */
|
||
setup[4] = 0x00;
|
||
setup[5] = 0x00;
|
||
setup[6] = (u8)(max_len & 0xFF);
|
||
setup[7] = (u8)((max_len >> 8) & 0xFF);
|
||
|
||
xhci_zero(out_desc, max_len);
|
||
return xhci_control_transfer(setup, out_desc, max_len, 1);
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 4: Bulk Transfer
|
||
* ================================================================ */
|
||
|
||
static int xhci_bulk_transfer(xhci_ring *ring, u8 ep_num,
|
||
u8 direction, /* 0=OUT, 1=IN */
|
||
const void *data, u32 data_len) {
|
||
if (data_len == 0) return -1;
|
||
|
||
/* For OUT, copy data to DMA buffer */
|
||
if (direction == 0) {
|
||
xhci_memcpy(g_xfer_buf.virt, data, data_len);
|
||
__asm__ volatile("wbinvd" ::: "memory");
|
||
}
|
||
|
||
/* Enqueue Normal TRB(s) — single TRB for now (up to 64KB) */
|
||
u32 control = (TRB_TYPE_NORMAL << TRB_TYPE_SHIFT) | TRB_IOC;
|
||
xhci_ring_enqueue(ring, g_xfer_buf.phys, data_len, control);
|
||
|
||
xhci_mb();
|
||
/* Doorbell target = Endpoint ID:EPn OUT→2n,EPn IN→2n+1。
|
||
* BUG-FIX: 原实现传 EP 号(2/1),QEMU xhci_kick_ep 按 Endpoint ID
|
||
* 索引 eps[epid-1] → 敲错端点 → 无 Transfer Event → 轮询超时。 */
|
||
u8 db_target = (direction ? (u8)(2u * ep_num + 1) : (u8)(2u * ep_num));
|
||
xhci_ring_doorbell(g_msc.slot_id, db_target);
|
||
|
||
/* Poll for Transfer Event */
|
||
u64 out_param = 0;
|
||
u32 out_status = 0;
|
||
int rc = xhci_poll_event(EVT_TYPE_TRANSFER, 10000,
|
||
&out_param, &out_status, 0);
|
||
if (rc != 0) {
|
||
g_log->warn("[xhci] bulk transfer failed");
|
||
log_hex("[xhci] bulk rc=", (u64)(i64)rc);
|
||
return rc;
|
||
}
|
||
|
||
/* For IN, copy data from DMA buffer */
|
||
if (direction == 1) {
|
||
xhci_memcpy((void *)data, g_xfer_buf.virt, data_len);
|
||
}
|
||
|
||
/* Clear EINT */
|
||
mmio_write32(g_cap_length + XHCI_OP_USBSTS,
|
||
mmio_read32(g_cap_length + XHCI_OP_USBSTS) | USBSTS_EINT);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 4: BOT (Bulk-Only Transport) + SCSI
|
||
* ================================================================ */
|
||
|
||
static int xhci_bot_send_cbw(u8 lun, u8 flags, u32 data_len,
|
||
const u8 *cb, u8 cb_len) {
|
||
u8 cbw[31];
|
||
xhci_zero(cbw, 31);
|
||
|
||
/* Signature */
|
||
cbw[0] = 0x55; cbw[1] = 0x53; cbw[2] = 0x42; cbw[3] = 0x43;
|
||
/* Tag (little-endian) */
|
||
u32 tag = g_msc.cbw_tag++;
|
||
cbw[4] = (u8)(tag & 0xFF);
|
||
cbw[5] = (u8)((tag >> 8) & 0xFF);
|
||
cbw[6] = (u8)((tag >> 16) & 0xFF);
|
||
cbw[7] = (u8)((tag >> 24) & 0xFF);
|
||
/* Data Transfer Length (little-endian) */
|
||
cbw[8] = (u8)(data_len & 0xFF);
|
||
cbw[9] = (u8)((data_len >> 8) & 0xFF);
|
||
cbw[10] = (u8)((data_len >> 16) & 0xFF);
|
||
cbw[11] = (u8)((data_len >> 24) & 0xFF);
|
||
/* Flags */
|
||
cbw[12] = flags;
|
||
/* LUN */
|
||
cbw[13] = lun;
|
||
/* CB Length */
|
||
cbw[14] = cb_len;
|
||
/* Command Block */
|
||
for (u8 i = 0; i < cb_len && i < 16; i++)
|
||
cbw[15 + i] = cb[i];
|
||
|
||
return xhci_bulk_transfer(&g_bulk_out_ring, g_msc.bulk_out_ep,
|
||
0, cbw, 31);
|
||
}
|
||
|
||
static int xhci_bot_receive_csw(void) {
|
||
int rc = xhci_bulk_transfer(&g_bulk_in_ring, g_msc.bulk_in_ep,
|
||
1, g_csw_buf.virt, 13);
|
||
if (rc != 0) return rc;
|
||
|
||
/* Verify CSW signature */
|
||
u8 *csw = (u8 *)g_csw_buf.virt;
|
||
if (csw[0] != 0x55 || csw[1] != 0x53 || csw[2] != 0x42 || csw[3] != 0x53) {
|
||
g_log->error("[xhci] CSW signature mismatch");
|
||
return -2;
|
||
}
|
||
|
||
/* CSW status: byte 12, 0=OK */
|
||
u8 status = csw[12];
|
||
if (status != 0) {
|
||
log_hex("[xhci] CSW status=", status);
|
||
return -3;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* SCSI INQUIRY */
|
||
static int xhci_scsi_inquiry(void) {
|
||
u8 cb[16];
|
||
xhci_zero(cb, 16);
|
||
cb[0] = SCSI_INQUIRY;
|
||
cb[4] = 36; /* allocation length */
|
||
|
||
int rc = xhci_bot_send_cbw(0, 0x80, 36, cb, 6);
|
||
if (rc != 0) return rc;
|
||
|
||
/* Data phase: IN 36 bytes */
|
||
u8 inq_data[36];
|
||
rc = xhci_bulk_transfer(&g_bulk_in_ring, g_msc.bulk_in_ep,
|
||
1, inq_data, 36);
|
||
if (rc != 0) return rc;
|
||
|
||
/* CSW */
|
||
rc = xhci_bot_receive_csw();
|
||
if (rc != 0) return rc;
|
||
|
||
/* Parse INQUIRY data */
|
||
u8 dev_type = inq_data[0] & 0x1F;
|
||
log_hex("[xhci] INQ PeripheralType=", dev_type);
|
||
g_log->info("[xhci] INQ Vendor:");
|
||
g_log->info((const char *)(inq_data + 8)); /* Not null-terminated but safe for log */
|
||
|
||
return (dev_type == 0x00) ? 0 : -1; /* 0 = Direct Access Block */
|
||
}
|
||
|
||
/* SCSI READ CAPACITY(10) */
|
||
static int xhci_scsi_read_capacity(u64 *out_sectors, u32 *out_blk_size) {
|
||
u8 cb[16];
|
||
xhci_zero(cb, 16);
|
||
cb[0] = SCSI_READ_CAPACITY10;
|
||
|
||
int rc = xhci_bot_send_cbw(0, 0x80, 8, cb, 10);
|
||
if (rc != 0) return rc;
|
||
|
||
/* Data phase: IN 8 bytes */
|
||
u8 cap_data[8];
|
||
rc = xhci_bulk_transfer(&g_bulk_in_ring, g_msc.bulk_in_ep,
|
||
1, cap_data, 8);
|
||
if (rc != 0) return rc;
|
||
|
||
/* CSW */
|
||
rc = xhci_bot_receive_csw();
|
||
if (rc != 0) return rc;
|
||
|
||
/* Parse big-endian */
|
||
u32 max_lba = ((u32)cap_data[0] << 24) | ((u32)cap_data[1] << 16) |
|
||
((u32)cap_data[2] << 8) | (u32)cap_data[3];
|
||
u32 blk_size = ((u32)cap_data[4] << 24) | ((u32)cap_data[5] << 16) |
|
||
((u32)cap_data[6] << 8) | (u32)cap_data[7];
|
||
|
||
*out_sectors = (u64)max_lba + 1; /* READ CAPACITY returns max LBA */
|
||
*out_blk_size = blk_size;
|
||
|
||
log_hex("[xhci] capacity sectors=", *out_sectors);
|
||
log_hex("[xhci] block size=", blk_size);
|
||
return 0;
|
||
}
|
||
|
||
/* SCSI TEST UNIT READY */
|
||
static int xhci_scsi_test_unit_ready(void) {
|
||
u8 cb[16];
|
||
xhci_zero(cb, 16);
|
||
cb[0] = SCSI_TEST_UNIT_READY;
|
||
|
||
int rc = xhci_bot_send_cbw(0, 0x00, 0, cb, 6);
|
||
if (rc != 0) return rc;
|
||
return xhci_bot_receive_csw();
|
||
}
|
||
|
||
/* SCSI READ(10) */
|
||
static int xhci_scsi_read10(u64 lba, u32 count, void *buffer) {
|
||
u8 cb[16];
|
||
xhci_zero(cb, 16);
|
||
cb[0] = SCSI_READ10;
|
||
cb[2] = (u8)((lba >> 24) & 0xFF);
|
||
cb[3] = (u8)((lba >> 16) & 0xFF);
|
||
cb[4] = (u8)((lba >> 8) & 0xFF);
|
||
cb[5] = (u8)(lba & 0xFF);
|
||
cb[7] = (u8)((count >> 8) & 0xFF);
|
||
cb[8] = (u8)(count & 0xFF);
|
||
|
||
u32 data_len = count * 512;
|
||
|
||
int rc = xhci_bot_send_cbw(0, 0x80, data_len, cb, 10);
|
||
if (rc != 0) return rc;
|
||
|
||
/* Data phase: IN */
|
||
rc = xhci_bulk_transfer(&g_bulk_in_ring, g_msc.bulk_in_ep,
|
||
1, buffer, data_len);
|
||
if (rc != 0) return rc;
|
||
|
||
return xhci_bot_receive_csw();
|
||
}
|
||
|
||
/* SCSI WRITE(10) */
|
||
static int xhci_scsi_write10(u64 lba, u32 count, const void *buffer) {
|
||
u8 cb[16];
|
||
xhci_zero(cb, 16);
|
||
cb[0] = SCSI_WRITE10;
|
||
cb[2] = (u8)((lba >> 24) & 0xFF);
|
||
cb[3] = (u8)((lba >> 16) & 0xFF);
|
||
cb[4] = (u8)((lba >> 8) & 0xFF);
|
||
cb[5] = (u8)(lba & 0xFF);
|
||
cb[7] = (u8)((count >> 8) & 0xFF);
|
||
cb[8] = (u8)(count & 0xFF);
|
||
|
||
u32 data_len = count * 512;
|
||
|
||
int rc = xhci_bot_send_cbw(0, 0x00, data_len, cb, 10);
|
||
if (rc != 0) return rc;
|
||
|
||
/* Data phase: OUT */
|
||
rc = xhci_bulk_transfer(&g_bulk_out_ring, g_msc.bulk_out_ep,
|
||
0, buffer, data_len);
|
||
if (rc != 0) return rc;
|
||
|
||
return xhci_bot_receive_csw();
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 4+5: Block device interface
|
||
* ================================================================ */
|
||
|
||
static int xhci_block_read(void *ctx, u64 lba, u32 count, void *buffer) {
|
||
(void)ctx;
|
||
if (!g_msc.ready || !buffer || count == 0) return -1;
|
||
DKM_STAT_INC(xhci_block_reads);
|
||
|
||
u8 *buf = (u8 *)buffer;
|
||
while (count > 0) {
|
||
u32 chunk = count > 16 ? 16 : count; /* max 16 sectors (8KB) per SCSI cmd */
|
||
int rc = xhci_scsi_read10(lba, chunk, g_xfer_buf.virt);
|
||
if (rc != 0) {
|
||
log_hex("[xhci] READ10 failed lba=", lba);
|
||
return rc;
|
||
}
|
||
xhci_memcpy(buf, g_xfer_buf.virt, chunk * 512);
|
||
buf += chunk * 512;
|
||
lba += chunk;
|
||
count -= chunk;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int xhci_block_write(void *ctx, u64 lba, u32 count, const void *buffer) {
|
||
(void)ctx;
|
||
if (!g_msc.ready || !buffer || count == 0) return -1;
|
||
DKM_STAT_INC(xhci_block_writes);
|
||
|
||
const u8 *buf = (const u8 *)buffer;
|
||
while (count > 0) {
|
||
u32 chunk = count > 16 ? 16 : count;
|
||
xhci_memcpy(g_xfer_buf.virt, buf, chunk * 512);
|
||
__asm__ volatile("wbinvd" ::: "memory"); /* Cache flush for DMA */
|
||
int rc = xhci_scsi_write10(lba, chunk, g_xfer_buf.virt);
|
||
if (rc != 0) {
|
||
log_hex("[xhci] WRITE10 failed lba=", lba);
|
||
return rc;
|
||
}
|
||
buf += chunk * 512;
|
||
lba += chunk;
|
||
count -= chunk;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 3: Build Input Context for Address Device
|
||
* ================================================================ */
|
||
|
||
static void xhci_build_input_ctx_slot(u8 slot_id, u8 speed, u16 mps0, u32 port_idx) {
|
||
(void)slot_id;
|
||
u32 ctx_sz = g_ctx_size;
|
||
u8 *ic = (u8 *)g_input_ctx.virt;
|
||
xhci_zero(ic, g_input_ctx.size);
|
||
|
||
/* Input Control Context (xHCI 1.2 §6.4.3):
|
||
* DWORD0 = Drop Context Flags(本次全 0,不 drop 任何上下文)
|
||
* DWORD1 = Add Context Flags(bit0=Slot, bit1=EP0)
|
||
* BUG-FIX: 原实现把 Add flags 写进 DWORD0(Drop 槽),
|
||
* QEMU/实机校验 ictl_ctx[0]==0 && ictl_ctx[1]==0x3 直接失败 CC_TRB_ERROR。 */
|
||
u32 *icc = (u32 *)ic;
|
||
icc[0] = 0x0; /* Drop Context Flags */
|
||
icc[1] = (1u << 0) | (1u << 1); /* Add: Slot + EP0 */
|
||
|
||
/* Slot Context (§6.2.2):
|
||
* DWORD0: Route String [0:19] + Speed [20:22] + Context Entries [27:31]
|
||
* DWORD1: Max Exit Latency [0:7] + Root Hub Port Number [16:23]
|
||
* BUG-FIX: 原实现 speed<<29(占用 Context Entries 位域)、MPS0 写入
|
||
* DWORD1(规范 Slot Context 无 MPS0 字段,MPS0 在 EP0 Context)——
|
||
* QEMU xhci_lookup_uport 读 DWORD1[16:23] 找端口,原值 port=0 查不到
|
||
* 设备 → CC_TRB_ERROR。 */
|
||
u32 *slot_ctx = (u32 *)(ic + ctx_sz);
|
||
u32 route_string = 0; /* direct-connect, no hub */
|
||
slot_ctx[0] = route_string | ((u32)speed << 20) | (1u << 27);
|
||
slot_ctx[1] = (port_idx + 1) << 16; /* Root Hub Port Number (1-based) */
|
||
|
||
/* EP0 Context starts at offset ctx_sz * 2
|
||
* Endpoint Context (§6.2.3) 布局:
|
||
* DWORD1: CErr[4:5] + EP Type[6:8] + Max Packet Size[16:31]
|
||
* DWORD2: Dequeue Pointer Low[4:31](bit0=DCS, bits1:3=reserved)
|
||
* DWORD3: Dequeue Pointer High
|
||
* DWORD4: Average TRB Length[0:15]
|
||
* BUG-FIX: 原实现把 dequeue 写到 DWORD6/7——QEMU xhci_init_epctx
|
||
* 从 DWORD2/3 读 dequeue(ctx[2]&~0xf, ctx[3]),读到 0 → ring 无
|
||
* dequeue → kick_epctx 直接返回,TRB 永不处理 → 无 Transfer Event。 */
|
||
u32 *ep0_ctx = (u32 *)(ic + ctx_sz * 2);
|
||
ep0_ctx[1] = (4u << 3) | ((u32)mps0 << 16); /* EP Type=4 (Control IN), MPS */
|
||
u64 ep0_deq = g_ep0_ring.dma.phys | (u64)g_ep0_ring.ccs;
|
||
ep0_ctx[2] = (u32)(ep0_deq & 0xFFFFFFF0u) | (u32)g_ep0_ring.ccs;
|
||
ep0_ctx[3] = (u32)(ep0_deq >> 32);
|
||
ep0_ctx[4] = 8; /* Average TRB Length */
|
||
}
|
||
|
||
static void xhci_build_input_ctx_bulk_eps(u8 bulk_out_ep, u8 bulk_in_ep,
|
||
u16 out_mps, u16 in_mps) {
|
||
u32 ctx_sz = g_ctx_size;
|
||
u8 *ic = (u8 *)g_input_ctx.virt;
|
||
|
||
/* Input Control Context(Configure Endpoint 语义,xHCI 1.2 §6.4.3):
|
||
* DWORD0 = Drop(0),DWORD1 = Add。
|
||
* Configure 时 bit0=Slot 必须 Add,bit1=EP0 不可重复 Add
|
||
* (QEMU xhci_configure_slot 校验 (ictl[1]&0x3)==0x1)。
|
||
* 端点位用 Context Index:EPn OUT→2n,EPn IN→2n+1
|
||
* (QEMU 从 i=2 开始遍历 Add flags,按 ictx+32*i 读 EP Context)。 */
|
||
u32 *icc = (u32 *)ic;
|
||
u32 out_idx = 2u * bulk_out_ep;
|
||
u32 in_idx = 2u * bulk_in_ep + 1;
|
||
icc[0] = 0x0;
|
||
icc[1] = (1u << 0) | (1u << out_idx) | (1u << in_idx);
|
||
|
||
/* Update Slot Context: Context Entries = max Context Index
|
||
* (DWORD0 bits[27:31],保留 Speed/Route 原值)。 */
|
||
u32 *slot_ctx = (u32 *)(ic + ctx_sz);
|
||
u32 max_idx = out_idx > in_idx ? out_idx : in_idx;
|
||
slot_ctx[0] &= ~(0x1Fu << 27);
|
||
slot_ctx[0] |= (max_idx << 27);
|
||
|
||
/* Bulk OUT Endpoint Context。
|
||
* 偏移 = ctx_sz * (out_idx + 1):input context 布局是
|
||
* [ICC][Slot][EP0][EP1]... 每个占 1 个 ctx_sz,Context Index i 的
|
||
* EP context 在 (i+1) 个槽处(QEMU xhci_configure_slot 用
|
||
* ictx+32+(32*i) 读取)。BUG-FIX: 原实现 ctx_sz*out_idx 少了 ICC
|
||
* 槽 → 全部错位 32 字节 → QEMU 读到零 EP context。 */
|
||
u32 *out_ctx = (u32 *)(ic + ctx_sz * (out_idx + 1));
|
||
out_ctx[1] = (EP_TYPE_BULK_OUT << 3) | ((u32)out_mps << 16);
|
||
u64 out_deq = g_bulk_out_ring.dma.phys | (u64)g_bulk_out_ring.ccs;
|
||
out_ctx[2] = (u32)(out_deq & 0xFFFFFFF0u) | (u32)g_bulk_out_ring.ccs;
|
||
out_ctx[3] = (u32)(out_deq >> 32);
|
||
out_ctx[4] = 512; /* Average TRB Length */
|
||
|
||
/* Bulk IN Endpoint Context (at offset ctx_sz * (in_idx + 1)) */
|
||
u32 *in_ctx = (u32 *)(ic + ctx_sz * (in_idx + 1));
|
||
in_ctx[1] = (EP_TYPE_BULK_IN << 3) | ((u32)in_mps << 16);
|
||
u64 in_deq = g_bulk_in_ring.dma.phys | (u64)g_bulk_in_ring.ccs;
|
||
in_ctx[2] = (u32)(in_deq & 0xFFFFFFF0u) | (u32)g_bulk_in_ring.ccs;
|
||
in_ctx[3] = (u32)(in_deq >> 32);
|
||
in_ctx[4] = 512; /* Average TRB Length */
|
||
}
|
||
|
||
/* ================================================================
|
||
* Phase 3: Full device enumeration
|
||
* ================================================================ */
|
||
|
||
static int xhci_enum_msc_device(u32 port_idx) {
|
||
u8 speed = xhci_get_port_speed(port_idx);
|
||
log_hex("[xhci] port speed=", speed);
|
||
|
||
/* Default MaxPacketSize for EP0 based on speed
|
||
* (xHCI 1.2 §4.3: Full=8? No — FS/HS EP0 通常 64,LS=8,SS=512) */
|
||
u16 mps0 = 64; /* Full/High-Speed default */
|
||
if (speed == USB_SPEED_LOW) mps0 = 8;
|
||
if (speed == USB_SPEED_SUPER) mps0 = 512;
|
||
|
||
/* Step 1: Enable Slot */
|
||
u8 slot_id = 0;
|
||
if (xhci_enable_slot(&slot_id) != 0) {
|
||
FB_ENUM_FAIL(0x0A); /* 黄: Enable Slot 失败 */
|
||
return -1;
|
||
}
|
||
g_msc.slot_id = slot_id;
|
||
g_msc.speed = speed;
|
||
|
||
/* Step 1.5: Allocate per-device DMA buffers(在 DCBAA 指向输出上下文之前) */
|
||
if (g_api->dma->alloc_pages(4, 4096, 0x100000000ULL, &g_input_ctx) != 0) return -2;
|
||
if (g_api->dma->alloc_pages(4, 4096, 0x100000000ULL, &g_dev_ctx) != 0) return -2;
|
||
if (g_api->dma->alloc_pages(4, 4096, 0x100000000ULL, &g_xfer_buf) != 0) return -2;
|
||
if (g_api->dma->alloc_pages(1, 4096, 0x100000000ULL, &g_csw_buf) != 0) return -2;
|
||
if (xhci_ring_init(&g_ep0_ring, XFER_RING_SIZE) != 0) return -2;
|
||
if (xhci_ring_init(&g_bulk_out_ring, XFER_RING_SIZE) != 0) return -2;
|
||
if (xhci_ring_init(&g_bulk_in_ring, XFER_RING_SIZE) != 0) return -2;
|
||
|
||
/* DCBAA[slot_id] 必须指向输出上下文(Device Context)。
|
||
* Address Device/Configure Endpoint 命令都会读取该地址;
|
||
* 缺失时 QEMU/实机将 octx=0 → DMA 写失败/CC_TRB_ERROR。 */
|
||
xhci_zero(g_dev_ctx.virt, g_dev_ctx.size);
|
||
u64 *dcbaa = (u64 *)g_dcbaa.virt;
|
||
dcbaa[slot_id] = g_dev_ctx.phys;
|
||
log_hex("[xhci] DCBAA[slot]=", dcbaa[slot_id]);
|
||
|
||
/* Step 3: Build Input Context and Address Device (BSR=1 first) */
|
||
xhci_build_input_ctx_slot(slot_id, speed, mps0, port_idx);
|
||
if (xhci_address_device(slot_id, 1) != 0) {
|
||
/* BSR=1 failed, try without BSR */
|
||
xhci_build_input_ctx_slot(slot_id, speed, mps0, port_idx);
|
||
if (xhci_address_device(slot_id, 0) != 0) {
|
||
FB_ENUM_FAIL(0x0B); /* 黄: Address Device 失败(BSR=1/0 均失败) */
|
||
return -3;
|
||
}
|
||
}
|
||
|
||
/* Step 4: Get Device Descriptor */
|
||
u8 dev_desc[18];
|
||
if (xhci_get_device_descriptor(dev_desc) != 0) {
|
||
g_log->warn("[xhci] GET_DESCRIPTOR(Device) failed");
|
||
FB_ENUM_FAIL(0x0C); /* 黄: GET_DESCRIPTOR(Device) 失败 */
|
||
return -4;
|
||
}
|
||
|
||
u8 dev_class = dev_desc[4];
|
||
u16 vid = (u16)dev_desc[8] | ((u16)dev_desc[9] << 8);
|
||
u16 pid = (u16)dev_desc[10] | ((u16)dev_desc[11] << 8);
|
||
u8 real_mps0 = dev_desc[7];
|
||
|
||
log_hex("[xhci] dev class=", dev_class);
|
||
log_hex("[xhci] VID=", vid);
|
||
log_hex("[xhci] PID=", pid);
|
||
log_hex("[xhci] bMaxPacketSize0=", real_mps0);
|
||
|
||
/* Step 5: Re-address with real MPS if different */
|
||
if (real_mps0 != mps0 && real_mps0 >= 8) {
|
||
mps0 = real_mps0;
|
||
xhci_build_input_ctx_slot(slot_id, speed, mps0, port_idx);
|
||
if (xhci_address_device(slot_id, 0) != 0) {
|
||
g_log->warn("[xhci] re-address failed");
|
||
/* Continue anyway — device may still work */
|
||
}
|
||
}
|
||
|
||
/* Step 6: Get Configuration Descriptor */
|
||
u8 cfg_desc[255];
|
||
if (xhci_get_config_descriptor(cfg_desc, 255) != 0) {
|
||
g_log->warn("[xhci] GET_DESCRIPTOR(Config) failed");
|
||
FB_ENUM_FAIL(0x0C); /* 黄: GET_DESCRIPTOR(Config) 失败 */
|
||
return -5;
|
||
}
|
||
|
||
/* Parse configuration descriptor to find MSC interface and endpoints */
|
||
u16 total_len = (u16)cfg_desc[2] | ((u16)cfg_desc[3] << 8);
|
||
if (total_len < 9) {
|
||
FB_ENUM_FAIL(0x0D); /* 黄: 配置描述符过短 */
|
||
return -5;
|
||
}
|
||
|
||
u8 bulk_out_ep = 0, bulk_in_ep = 0;
|
||
u16 bulk_out_mps = 512, bulk_in_mps = 512;
|
||
int msc_found = 0;
|
||
|
||
/* Walk through descriptors: config(9) + interface(9) + endpoints(7*N) */
|
||
u32 pos = 9; /* skip config descriptor */
|
||
while (pos + 2 <= (u32)total_len && pos + cfg_desc[pos] <= (u32)total_len) {
|
||
u8 desc_len = cfg_desc[pos];
|
||
u8 desc_type = cfg_desc[pos + 1];
|
||
|
||
if (desc_type == 4 && desc_len >= 9) {
|
||
/* Interface Descriptor */
|
||
u8 iface_class = cfg_desc[pos + 5];
|
||
u8 iface_subclass = cfg_desc[pos + 6];
|
||
u8 iface_protocol = cfg_desc[pos + 7];
|
||
|
||
if (iface_class == 0x08 && iface_subclass == 0x06 && iface_protocol == 0x50) {
|
||
msc_found = 1;
|
||
g_log->info("[xhci] MSC BOT interface found");
|
||
|
||
/* Scan following endpoint descriptors.
|
||
* SuperSpeed 设备在 endpoint 后紧跟 6 字节 SS Endpoint
|
||
* Companion descriptor (type 0x30)——必须跳过继续找下一
|
||
* 个 endpoint,不能因 type!=5 提前终止(Bulk OUT 常在
|
||
* IN 之后且被 companion 隔开)。 */
|
||
u32 ep_pos = pos + desc_len;
|
||
while (ep_pos + 2 <= (u32)total_len) {
|
||
u8 ep_len = cfg_desc[ep_pos];
|
||
if (ep_len == 0 || ep_pos + ep_len > (u32)total_len) break;
|
||
if (cfg_desc[ep_pos + 1] == 5) { /* endpoint descriptor */
|
||
u8 ep_addr = cfg_desc[ep_pos + 2];
|
||
u8 ep_attr = cfg_desc[ep_pos + 3];
|
||
u16 ep_mps = (u16)cfg_desc[ep_pos + 4] | ((u16)cfg_desc[ep_pos + 5] << 8);
|
||
|
||
if ((ep_attr & 0x03) == 0x02) { /* Bulk */
|
||
if (ep_addr & 0x80) {
|
||
bulk_in_ep = ep_addr & 0x0F;
|
||
bulk_in_mps = ep_mps;
|
||
} else {
|
||
bulk_out_ep = ep_addr & 0x0F;
|
||
bulk_out_mps = ep_mps;
|
||
}
|
||
}
|
||
} else if (cfg_desc[ep_pos + 1] == 4) {
|
||
/* 下一个 interface — 停止扫描 */
|
||
break;
|
||
}
|
||
ep_pos += ep_len;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
pos += desc_len;
|
||
}
|
||
|
||
if (!msc_found || bulk_out_ep == 0 || bulk_in_ep == 0) {
|
||
g_log->warn("[xhci] no MSC BOT interface found");
|
||
FB_ENUM_FAIL(0x0D); /* 黄: 未找到 MSC BOT 接口/端点 */
|
||
return -6;
|
||
}
|
||
|
||
log_hex("[xhci] Bulk OUT EP=", bulk_out_ep);
|
||
log_hex("[xhci] Bulk IN EP=", bulk_in_ep);
|
||
log_hex("[xhci] Bulk OUT MPS=", bulk_out_mps);
|
||
log_hex("[xhci] Bulk IN MPS=", bulk_in_mps);
|
||
|
||
g_msc.bulk_out_ep = bulk_out_ep;
|
||
g_msc.bulk_in_ep = bulk_in_ep;
|
||
g_msc.bulk_out_mps = bulk_out_mps;
|
||
g_msc.bulk_in_mps = bulk_in_mps;
|
||
|
||
/* Step 7: Configure Endpoint */
|
||
xhci_zero(g_input_ctx.virt, g_input_ctx.size);
|
||
xhci_build_input_ctx_bulk_eps(bulk_out_ep, bulk_in_ep,
|
||
bulk_out_mps, bulk_in_mps);
|
||
if (xhci_configure_endpoint(slot_id) != 0) {
|
||
g_log->warn("[xhci] Configure Endpoint failed");
|
||
FB_ENUM_FAIL(0x0E); /* 黄: Configure Endpoint 失败 */
|
||
return -7;
|
||
}
|
||
|
||
/* Step 8: SCSI INQUIRY to verify MSC device */
|
||
if (xhci_scsi_inquiry() != 0) {
|
||
g_log->warn("[xhci] INQUIRY failed or not block device");
|
||
/* Continue anyway — some devices return non-zero peripheral type */
|
||
}
|
||
|
||
/* Step 9: TEST UNIT READY (retry up to 5 times) */
|
||
for (int i = 0; i < 5; i++) {
|
||
if (xhci_scsi_test_unit_ready() == 0) break;
|
||
dkm_delay_ms(100);
|
||
}
|
||
|
||
/* Step 10: READ CAPACITY */
|
||
u64 sectors = 0;
|
||
u32 blk_size = 512;
|
||
if (xhci_scsi_read_capacity(§ors, &blk_size) != 0) {
|
||
g_log->warn("[xhci] READ CAPACITY failed");
|
||
/* Use defaults */
|
||
sectors = 0;
|
||
blk_size = 512;
|
||
}
|
||
|
||
g_msc.sector_count = sectors;
|
||
g_msc.sector_size = blk_size;
|
||
g_msc.cbw_tag = 1;
|
||
g_msc.ready = 1;
|
||
|
||
DKM_STAT_INC(xhci_msc_found);
|
||
log_hex("[xhci] MSC device ready, sectors=", sectors);
|
||
log_hex("[xhci] MSC device block_size=", blk_size);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* ================================================================
|
||
* driver_init — main entry point
|
||
* ================================================================ */
|
||
|
||
#define XHCI_TRY_NEXT do { xhci_nth++; if (xhci_nth < 4) goto again_ctrl; return 0; } while (0)
|
||
|
||
__attribute__((visibility("default")))
|
||
int driver_init(const struct dkm_kernel_api *api,
|
||
struct dkm_driver_handle *handle) {
|
||
(void)handle;
|
||
dkm_tsc_calibrate();
|
||
|
||
if (!api || !api->log) return -1;
|
||
g_api = api;
|
||
g_log = api->log;
|
||
dkm_instr_init(api);
|
||
|
||
g_log->info("[xhci] init begin");
|
||
|
||
/* ---- Phase 1: PCI discovery ---- */
|
||
u8 bus = 0, dev = 0, func = 0;
|
||
/* 实机常有不止一只 xHCI。U 盘可能不在第一只上,找不到 MSC 就试下一只。 */
|
||
int xhci_nth = 0;
|
||
again_ctrl:
|
||
if (xhci_find_nth(xhci_nth, &bus, &dev, &func) != 0) {
|
||
g_log->info("[xhci] xHCI controller not found");
|
||
/* BUG-FIX: 不 halt。xhci 是可选驱动(manifest required=false),
|
||
* 无 xHCI 的平台(Intel 6 系/纯 EHCI)必须继续引导到 EHCI/DSK,
|
||
* 否则 `for(;;)hlt` 卡死整条启动链。仅记日志,如需诊断可开
|
||
* xhci_pci_diag_code() 打印控制器清单(见注释)。 */
|
||
return 0;
|
||
}
|
||
|
||
g_pci_bus = bus; g_pci_dev = dev; g_pci_func = func;
|
||
DKM_STAT_INC(xhci_pci_found);
|
||
g_log->info("[xhci] xHCI controller found");
|
||
log_hex("[xhci] bus=", bus);
|
||
log_hex("[xhci] dev=", dev);
|
||
log_hex("[xhci] func=", func);
|
||
|
||
u32 vd = dkm_pci_read(bus, dev, func, PCI_VENDOR_ID);
|
||
log_hex("[xhci] vendor=", vd & 0xffff);
|
||
log_hex("[xhci] device=", vd >> 16);
|
||
|
||
/* Intel PCH 默认把 USB2 口留在 EHCI。不把路由切到 xHCI,
|
||
* U 盘在 xHCI 上永远没有 CCS,块设备就不会注册。 */
|
||
if ((vd & 0xffff) == 0x8086) {
|
||
u32 usb2_mask = dkm_pci_read(bus, dev, func, 0xD4);
|
||
u32 usb3_mask = dkm_pci_read(bus, dev, func, 0xDC);
|
||
if (usb2_mask) dkm_pci_write(bus, dev, func, 0xD0, usb2_mask);
|
||
if (usb3_mask) dkm_pci_write(bus, dev, func, 0xD8, usb3_mask);
|
||
log_hex("[xhci] intel usb2 route=", usb2_mask);
|
||
log_hex("[xhci] intel usb3 route=", usb3_mask);
|
||
dkm_delay_ms(200);
|
||
}
|
||
|
||
/* Enable PCI MEM + BUSM, disable INTx (polling mode) */
|
||
u32 command = dkm_pci_read(bus, dev, func, PCI_COMMAND) & 0xffff;
|
||
command |= PCI_CMD_MEM | PCI_CMD_BUSM | (1u << 10);
|
||
dkm_pci_write(bus, dev, func, PCI_COMMAND, command);
|
||
log_hex("[xhci] PCI command=", command);
|
||
|
||
/* Read BAR0 (64-bit) */
|
||
u32 bar0_lo = dkm_pci_read(bus, dev, func, PCI_BAR0);
|
||
u32 bar1_hi = dkm_pci_read(bus, dev, func, PCI_BAR1);
|
||
u64 bar_phys;
|
||
if (bar0_lo & 1u) {
|
||
g_log->warn("[xhci] BAR0 is IO space; unsupported");
|
||
FB_FATAL(api, 0x02); /* 红: BAR0 是 IO 空间 */
|
||
XHCI_TRY_NEXT;
|
||
}
|
||
if ((bar0_lo & 0x6u) == 0x4u) {
|
||
bar_phys = ((u64)bar1_hi << 32) | (u64)(bar0_lo & 0xFFFFFFF0u);
|
||
} else {
|
||
bar_phys = (u64)(bar0_lo & 0xFFFFFFF0u);
|
||
}
|
||
|
||
u32 irq_line = dkm_pci_read(bus, dev, func, PCI_IRQ_LINE) & 0xff;
|
||
log_hex("[xhci] BAR0 phys=", bar_phys);
|
||
log_hex("[xhci] IRQ line=", irq_line);
|
||
log_hex("[xhci] HHDM offset=", api->hhdm_offset);
|
||
|
||
if (!api->hhdm_offset || !bar_phys) {
|
||
g_log->warn("[xhci] missing HHDM or BAR0; skip");
|
||
FB_FATAL(api, 0x02); /* 红: 缺 HHDM/BAR */
|
||
XHCI_TRY_NEXT;
|
||
}
|
||
|
||
/* ---- Phase 1: MMIO mapping ---- */
|
||
g_mmio = xhci_map_bar(api, bar_phys);
|
||
if (!g_mmio) {
|
||
g_log->error("[xhci] BAR MMIO unavailable; driver inactive");
|
||
FB_FATAL(api, 0x03); /* 红: BAR MMIO 映射失败 */
|
||
XHCI_TRY_NEXT;
|
||
}
|
||
|
||
/* ---- Phase 1: Parse capabilities ---- */
|
||
if (xhci_parse_caps() != 0) {
|
||
g_log->error("[xhci] capability parse failed");
|
||
FB_FATAL(api, 0x04); /* 红: capability 解析失败 */
|
||
XHCI_TRY_NEXT;
|
||
}
|
||
|
||
/* ---- Phase 1: BIOS Handoff ---- */
|
||
xhci_bios_handoff();
|
||
|
||
/* ---- Phase 1: Controller Reset ---- */
|
||
if (xhci_controller_reset() != 0) {
|
||
g_log->error("[xhci] controller reset failed");
|
||
FB_FATAL(api, 0x05); /* 红: 控制器 reset 失败 */
|
||
XHCI_TRY_NEXT;
|
||
}
|
||
|
||
/* ---- Phase 2: Setup Rings + DCBAA + Scratchpad ---- */
|
||
if (!api->dma) {
|
||
g_log->warn("[xhci] DMA API unavailable");
|
||
FB_FATAL(api, 0x06); /* 红: DMA API 缺失 */
|
||
XHCI_TRY_NEXT;
|
||
}
|
||
|
||
if (xhci_setup_rings() != 0) {
|
||
g_log->error("[xhci] ring setup failed");
|
||
FB_FATAL(api, 0x06); /* 红: ring 初始化失败 */
|
||
XHCI_TRY_NEXT;
|
||
}
|
||
|
||
/* ---- Phase 2: Start Controller ---- */
|
||
if (xhci_controller_start() != 0) {
|
||
g_log->error("[xhci] controller start failed");
|
||
FB_FATAL(api, 0x07); /* 红: 控制器启动失败 */
|
||
XHCI_TRY_NEXT;
|
||
}
|
||
|
||
/* ---- Phase 3: Port Scan + Device Enumeration ---- */
|
||
|
||
/*
|
||
* Wait 100ms after controller start for QEMU to complete USB device
|
||
* attachment and update port status. Without this delay, PORTSC.CCS
|
||
* may read 0 even though a device is physically connected.
|
||
*/
|
||
/* 控制器复位后端口掉电。先全部上电,再等 U 盘重新连接。
|
||
* 20ms 就读 CCS 时,实机上的 U 盘还没出现,于是不会注册块设备。 */
|
||
for (u32 port = 0; port < g_max_ports; port++) {
|
||
u32 port_off = g_port_base + port * XHCI_PORT_STRIDE + XHCI_PORT_SC;
|
||
u32 portsc = mmio_read32(port_off);
|
||
if (g_ppc && !(portsc & (1u << 9))) {
|
||
mmio_write32(port_off, portsc | (1u << 9));
|
||
}
|
||
}
|
||
g_log->info("[xhci] waiting 1500ms for port settle...");
|
||
dkm_delay_ms(1500);
|
||
|
||
g_msc.ready = 0;
|
||
int saw_ccs = 0;
|
||
int saw_reset_fail = 0;
|
||
for (int pass = 0; pass < 2 && !g_msc.ready; pass++) {
|
||
if (pass == 1) {
|
||
g_log->info("[xhci] second port scan");
|
||
dkm_delay_ms(1500);
|
||
}
|
||
for (u32 port = 0; port < g_max_ports; port++) {
|
||
u32 port_off = g_port_base + port * XHCI_PORT_STRIDE + XHCI_PORT_SC;
|
||
u32 portsc = mmio_read32(port_off);
|
||
|
||
log_hex("[xhci] port=", port);
|
||
log_hex("[xhci] PORTSC=", portsc);
|
||
|
||
if (!(portsc & PORTSC_CCS)) continue;
|
||
|
||
saw_ccs = 1;
|
||
g_log->info("[xhci] device connected");
|
||
|
||
if (xhci_reset_port(port) != 0) {
|
||
g_log->warn("[xhci] port reset failed");
|
||
saw_reset_fail = 1;
|
||
continue;
|
||
}
|
||
g_log->info("[xhci] port reset OK");
|
||
|
||
if (xhci_enum_msc_device(port) == 0) {
|
||
g_log->info("[xhci] MSC enumeration successful");
|
||
break;
|
||
}
|
||
|
||
g_log->warn("[xhci] MSC enumeration failed; trying next port");
|
||
}
|
||
}
|
||
if (!g_msc.ready && saw_ccs && xhci_nth >= 3) {
|
||
g_log->panic(saw_reset_fail ? "XHC-E01 PORT RESET FAILED"
|
||
: "XHC-E02 MSC ENUM FAILED");
|
||
}
|
||
|
||
/* ---- Phase 5: Register Block Device ---- */
|
||
if (g_msc.ready) {
|
||
if (api->block && api->block->register_device) {
|
||
struct dkm_block_device_desc desc;
|
||
desc.name = "usb0";
|
||
desc.sector_size = g_msc.sector_size;
|
||
desc.sector_count = g_msc.sector_count;
|
||
desc.ctx = 0;
|
||
desc.read = xhci_block_read;
|
||
desc.write = xhci_block_write;
|
||
int index = api->block->register_device(&desc);
|
||
log_hex("[xhci] block provider index=", (u64)(i64)index);
|
||
} else {
|
||
g_log->warn("[xhci] block API unavailable; provider not registered");
|
||
}
|
||
} else {
|
||
g_log->info("[xhci] no MSC device found; block provider not registered");
|
||
if (xhci_nth < 3) {
|
||
xhci_nth++;
|
||
g_log->info("[xhci] trying next controller");
|
||
goto again_ctrl;
|
||
}
|
||
FB_FATAL(api, 0x08); /* 黄: 所有端口无设备/枚举全部失败 */
|
||
}
|
||
|
||
g_log->info("[xhci] driver ready");
|
||
return 0;
|
||
}
|
||
#undef XHCI_TRY_NEXT
|
||
|
||
__attribute__((visibility("default")))
|
||
int driver_exit(struct dkm_driver_handle *handle) {
|
||
(void)handle;
|
||
/* Halt controller if it was started */
|
||
if (g_mmio) {
|
||
u32 usbcmd = mmio_read32(g_cap_length + XHCI_OP_USBCMD);
|
||
mmio_write32(g_cap_length + XHCI_OP_USBCMD, usbcmd & ~USBCMD_RS);
|
||
}
|
||
return 0;
|
||
}
|