318 lines
14 KiB
C
318 lines
14 KiB
C
/* mouseInit — PS/2 鼠标初始化器(完整初始化 + IRQ12 包解码 + 共享状态)
|
||
* DSK 直接调度的系统输入初始化器,初始化完成后返回 DSK,不驻留阻塞启动流程。
|
||
* 最高准则:任何单步失败只记日志继续执行,绝不阻塞或崩溃首次启动。
|
||
*
|
||
* ==================== 初始化命令序列 ====================
|
||
* 1. ps2_drain() 冲刷控制器残留数据;
|
||
* 2. 0x20 读配置字节 → 清 bit5(启用 AUX 时钟)、置 bit1(启用 AUX IRQ)、
|
||
* 置 bit6(Set2→Set1 翻译)→ 0x60 写回;
|
||
* 3. 0xA8 启用 AUX 端口;
|
||
* 4. 0xFF 复位鼠标 → 再 drain 最多 8 字节(ACK 0xFA / 自检 0xAA / ID 0x00);
|
||
* 5. 0xF6 Set Defaults → 读 ACK;
|
||
* 6. 0xF3 + 100 设置采样率 → 两次 ACK;
|
||
* 7. 0xE8 + 2 设置分辨率(4 counts/mm)→ 两次 ACK;
|
||
* 8. 0xF4 启用数据报告 → 读 ACK。
|
||
* 全程 ps2_wait_write/ps2_wait_read 100000 次超时保护,防死锁。
|
||
*
|
||
* ==================== IRQ12 包解码 ====================
|
||
* 经 kernel_api.irq_register 注册 IRQ12 handler。标准 3 字节包:
|
||
* byte0: bit7 Y溢出 bit6 X溢出 bit5 Y符号 bit4 X符号 bit3 恒1(同步位)
|
||
* bit2 中键 bit1 右键 bit0 左键
|
||
* byte1: X 位移低 8 位;byte2: Y 位移低 8 位
|
||
* 解码规则:首字节无同步位 → 丢弃并重新同步;溢出位置位 → 丢包计 overflow;
|
||
* dx/dy 按 9 位有符号合成(符号位扩展);PS/2 Y 向上为正、屏幕 Y 向下为正,
|
||
* 故 y -= dy;坐标累计后按 framebuffer 尺寸钳位到 [0,max_x/max_y]。
|
||
* handler 读 0x60 前必查状态寄存器 bit5(AUX 位):键盘数据一律不读,
|
||
* 留在输出缓冲中交给键盘路径,避免吞掉键盘扫描码。
|
||
*
|
||
* ==================== handler 存活性结论(重要) ====================
|
||
* DSK 的 dsk_load_elf 将所有 DSK 调度模块(mouseInit/netman/FirstInit/login/
|
||
* desktop)加载进同一块 1MB 静态镜像缓冲:mouseInit 返回后,下一个模块的
|
||
* 加载会整体覆盖本镜像。因此本文件中的 IRQ12 handler 与 g_mouse_state 只在
|
||
* mouseInit 驻留期间有效,不能作为跨模块的长期中断处理点。
|
||
*
|
||
* 本文件严格维持以下安全不变式,保证注册不形成野指针执行:
|
||
* 1) UTSM pic_remap 后从片 PIC 屏蔽字为 0xFF,IRQ12 保持屏蔽,本文件不解屏蔽;
|
||
* 2) DSK dsk_entry 已 cli,整个 DSK 阶段(含 desktop)IF=0,无任何模块 sti;
|
||
* => handler 已注册进内核 g_irq_handlers[12],但永远不会被调用;
|
||
* 镜像被后续模块覆盖后也不存在执行风险。
|
||
*
|
||
* 后续集成路径(二选一,均需本目录以外的配套改动,不在本文件范围):
|
||
* a) DKM 输入驱动:把本文件解码逻辑搬进内核常驻 .drv(参照 ps2kbd),
|
||
* 由它注册 IRQ12 并解屏蔽从片 bit4,desktop 改读共享状态;
|
||
* b) DSK 加载器增加“常驻”标志,mouseInit 镜像不被复用后,再解屏蔽 IRQ12。
|
||
* 启用 IRQ 驱动后,desktop 等消费者必须切换为读共享状态而非轮询端口
|
||
* (handler 读走 0x60 后,轮询者将读不到数据)。在此之前,FirstInit/desktop
|
||
* 维持现有端口轮询——它们均检查 AUX 位,与本初始化完全兼容:本初始化启用
|
||
* 数据报告后,轮询者照常读到鼠标包。
|
||
*
|
||
* ==================== mouse_state_t 消费契约 ====================
|
||
* g_mouse_state 为导出全局符号,其布局即 ABI(version 字段标识版本)。
|
||
* 消费者先校验 magic == MOUSE_STATE_MAGIC 与 version,再读 x/y/buttons 及
|
||
* 计数器;或调用 mouse_poll_state() 取得只读快照。
|
||
* 注意:当前 DSK 加载器无跨模块符号解析,且镜像会被覆盖——消费方真正落地
|
||
* 依赖上述集成路径 a/b 提供常驻内存后的地址发布(例如经 ctx->reserved 槽位
|
||
* 传递状态指针,需与 DSK 协调 ABI)。
|
||
*/
|
||
|
||
#include "../UTSM/include/utsm/dsk.h"
|
||
#include "../UTSM/include/utsm/dkm.h"
|
||
#include "../UTSM/include/utsm/idt.h"
|
||
|
||
#define COM1 0x3F8
|
||
|
||
/* ---- PS/2 端口与状态位 ---- */
|
||
#define PS2_DATA 0x60
|
||
#define PS2_STATUS 0x64
|
||
#define PS2_CMD 0x64
|
||
#define PS2_ST_OBF 0x01 /* 输出缓冲满 */
|
||
#define PS2_ST_IBF 0x02 /* 输入缓冲满 */
|
||
#define PS2_ST_AUX 0x20 /* 输出数据来自 AUX(鼠标) */
|
||
#define PS2_TIMEOUT 100000u /* 读写等待超时次数,防死锁 */
|
||
|
||
/* ---- 控制器 / 鼠标命令 ---- */
|
||
#define PS2_CMD_READ_CFG 0x20 /* 读配置字节 */
|
||
#define PS2_CMD_WRITE_CFG 0x60 /* 写配置字节 */
|
||
#define PS2_CMD_ENABLE_AUX 0xA8 /* 启用 AUX 端口 */
|
||
#define PS2_CMD_TO_MOUSE 0xD4 /* 下一字节发往鼠标 */
|
||
#define MOUSE_CMD_RESET 0xFF
|
||
#define MOUSE_CMD_DEFAULTS 0xF6
|
||
#define MOUSE_CMD_RATE 0xF3 /* 设置采样率,跟一个字节参数 */
|
||
#define MOUSE_CMD_RESOL 0xE8 /* 设置分辨率,跟一个字节参数 */
|
||
#define MOUSE_CMD_ENABLE 0xF4 /* 启用数据报告 */
|
||
#define MOUSE_ACK 0xFA
|
||
|
||
#define IRQ_MOUSE 12
|
||
|
||
#define MOUSE_STATE_MAGIC 0x4D4F5553u /* "MOUS" */
|
||
#define MOUSE_STATE_VERSION 1u
|
||
|
||
/* 鼠标共享状态(布局即 ABI,见文件顶部“消费契约”) */
|
||
typedef struct mouse_state {
|
||
u32 magic; /* MOUSE_STATE_MAGIC,消费者校验用 */
|
||
u32 version; /* 布局版本,当前为 1 */
|
||
volatile i32 x; /* 光标 X(屏幕边界钳位后) */
|
||
volatile i32 y; /* 光标 Y */
|
||
volatile u32 buttons; /* bit0=左键 bit1=右键 bit2=中键 */
|
||
volatile u32 packet_count; /* 成功解码包数 */
|
||
volatile u32 overflow_count;/* 溢出 / 失步丢弃计数 */
|
||
i32 max_x; /* 钳位右边界 = fb_width - 1 */
|
||
i32 max_y; /* 钳位下边界 = fb_height - 1 */
|
||
} mouse_state_t;
|
||
|
||
__attribute__((visibility("default")))
|
||
mouse_state_t g_mouse_state;
|
||
|
||
/* 3 字节包组包缓冲(仅 IRQ handler 使用) */
|
||
static u8 g_pkt[3];
|
||
static u8 g_pkt_idx;
|
||
|
||
static inline void outb(u16 port, u8 value) { __asm__ volatile("outb %0,%1"::"a"(value),"Nd"(port)); }
|
||
static inline u8 inb(u16 port) { u8 v; __asm__ volatile("inb %1,%0":"=a"(v):"Nd"(port)); return v; }
|
||
|
||
static void sputc(char c) {
|
||
for (u32 i=0;i<10000;i++) if (inb(COM1+5)&0x20) break;
|
||
outb(COM1,(u8)c);
|
||
}
|
||
static void swrite(const char *s) { while(*s){ if(*s=='\n') sputc('\r'); sputc(*s++);} }
|
||
static void logl(const char *s) { swrite(s); swrite("\n"); }
|
||
static void logh(const char *s, u64 v) {
|
||
swrite(s);
|
||
for (int i = 15; i >= 0; i--) {
|
||
u8 n = (u8)((v >> (i * 4)) & 0xF);
|
||
sputc(n < 10 ? (char)('0' + n) : (char)('A' + n - 10));
|
||
}
|
||
swrite("\n");
|
||
}
|
||
|
||
/* ---- PS/2 基础等待与冲刷(全程超时保护) ---- */
|
||
static int ps2_wait_write(void) {
|
||
for (u32 t = 0; t < PS2_TIMEOUT; t++) {
|
||
if (!(inb(PS2_STATUS) & PS2_ST_IBF)) return 0;
|
||
__asm__ volatile("pause");
|
||
}
|
||
return -1;
|
||
}
|
||
static int ps2_wait_read(void) {
|
||
for (u32 t = 0; t < PS2_TIMEOUT; t++) {
|
||
if (inb(PS2_STATUS) & PS2_ST_OBF) return 0;
|
||
__asm__ volatile("pause");
|
||
}
|
||
return -1;
|
||
}
|
||
static void ps2_drain(void) {
|
||
for (int i = 0; i < 16; i++) {
|
||
if (!(inb(PS2_STATUS) & PS2_ST_OBF)) break;
|
||
inb(PS2_DATA);
|
||
}
|
||
}
|
||
static int ps2_cmd(u8 cmd) {
|
||
if (ps2_wait_write() != 0) return -1;
|
||
outb(PS2_CMD, cmd);
|
||
return 0;
|
||
}
|
||
|
||
/* ---- 鼠标命令:0xD4 前缀 + 命令字节 + 等 ACK(0xFA),返回 0 成功 ---- */
|
||
static int mouse_cmd(u8 cmd) {
|
||
if (ps2_cmd(PS2_CMD_TO_MOUSE) != 0) return -1;
|
||
if (ps2_wait_write() != 0) return -2;
|
||
outb(PS2_DATA, cmd);
|
||
if (ps2_wait_read() != 0) return -3;
|
||
return inb(PS2_DATA) == MOUSE_ACK ? 0 : -4;
|
||
}
|
||
/* 带单字节参数的鼠标命令(0xF3 采样率 / 0xE8 分辨率),命令与参数各等一次 ACK */
|
||
static int mouse_cmd_param(u8 cmd, u8 param) {
|
||
int rc = mouse_cmd(cmd);
|
||
if (rc != 0) return rc;
|
||
if (ps2_cmd(PS2_CMD_TO_MOUSE) != 0) return -5;
|
||
if (ps2_wait_write() != 0) return -6;
|
||
outb(PS2_DATA, param);
|
||
if (ps2_wait_read() != 0) return -7;
|
||
return inb(PS2_DATA) == MOUSE_ACK ? 0 : -8;
|
||
}
|
||
|
||
/* ---- IRQ12 中断处理:读数据前必查 AUX 位,键盘数据一律不读。
|
||
* 返回 0 = 由内核发送 EOI(UTSM idt.c irq_handler_t 约定)。 ---- */
|
||
static int mouse_irq12_handler(u8 irq) {
|
||
(void)irq;
|
||
for (u32 n = 0; n < 8; n++) { /* 有界读取,防异常硬件下 IRQ 内死循环 */
|
||
u8 st = inb(PS2_STATUS);
|
||
if (!(st & PS2_ST_OBF)) break; /* 无数据 */
|
||
if (!(st & PS2_ST_AUX)) break; /* 键盘数据:留给键盘路径 */
|
||
u8 data = inb(PS2_DATA);
|
||
|
||
if (g_pkt_idx == 0 && !(data & 0x08)) {/* 首字节缺同步位:丢字节重新同步 */
|
||
g_mouse_state.overflow_count++;
|
||
continue;
|
||
}
|
||
g_pkt[g_pkt_idx++] = data;
|
||
if (g_pkt_idx < 3) continue;
|
||
g_pkt_idx = 0;
|
||
|
||
u8 f = g_pkt[0];
|
||
if (f & 0xC0) { /* X/Y 溢出位:丢包 */
|
||
g_mouse_state.overflow_count++;
|
||
continue;
|
||
}
|
||
/* 9 位有符号位移合成:byte0 bit4/bit5 为 X/Y 符号位,扩展进第 9 位 */
|
||
i32 dx = (i32)g_pkt[1] - ((f & 0x10) ? 256 : 0);
|
||
i32 dy = (i32)g_pkt[2] - ((f & 0x20) ? 256 : 0);
|
||
i32 nx = g_mouse_state.x + dx;
|
||
i32 ny = g_mouse_state.y - dy; /* PS/2 Y 向上为正,屏幕 Y 向下为正 */
|
||
if (nx < 0) nx = 0;
|
||
if (ny < 0) ny = 0;
|
||
if (nx > g_mouse_state.max_x) nx = g_mouse_state.max_x;
|
||
if (ny > g_mouse_state.max_y) ny = g_mouse_state.max_y;
|
||
g_mouse_state.x = nx;
|
||
g_mouse_state.y = ny;
|
||
g_mouse_state.buttons = f & 0x07u;
|
||
g_mouse_state.packet_count++;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* ---- 只读查询接口:把当前状态快照拷贝给调用者,返回 0 成功 ---- */
|
||
__attribute__((visibility("default")))
|
||
int mouse_poll_state(mouse_state_t *out) {
|
||
if (!out) return -1;
|
||
if (g_mouse_state.magic != MOUSE_STATE_MAGIC) return -2;
|
||
out->magic = g_mouse_state.magic;
|
||
out->version = g_mouse_state.version;
|
||
out->x = g_mouse_state.x;
|
||
out->y = g_mouse_state.y;
|
||
out->buttons = g_mouse_state.buttons;
|
||
out->packet_count = g_mouse_state.packet_count;
|
||
out->overflow_count = g_mouse_state.overflow_count;
|
||
out->max_x = g_mouse_state.max_x;
|
||
out->max_y = g_mouse_state.max_y;
|
||
return 0;
|
||
}
|
||
|
||
static void mouse_state_reset(u64 fb_w, u64 fb_h) {
|
||
if (!fb_w) fb_w = 1024; /* 无 framebuffer 信息时的兜底边界 */
|
||
if (!fb_h) fb_h = 768;
|
||
g_mouse_state.magic = MOUSE_STATE_MAGIC;
|
||
g_mouse_state.version = MOUSE_STATE_VERSION;
|
||
g_mouse_state.max_x = (i32)fb_w - 1;
|
||
g_mouse_state.max_y = (i32)fb_h - 1;
|
||
g_mouse_state.x = (i32)fb_w / 2;
|
||
g_mouse_state.y = (i32)fb_h / 2;
|
||
g_mouse_state.buttons = 0;
|
||
g_mouse_state.packet_count = 0;
|
||
g_mouse_state.overflow_count = 0;
|
||
g_pkt_idx = 0;
|
||
}
|
||
|
||
__attribute__((visibility("default")))
|
||
void dsk_entry(const dsk_boot_context *ctx) {
|
||
logl("[mouseInit] boot");
|
||
|
||
/* 1. 冲刷残留数据 */
|
||
ps2_drain();
|
||
|
||
/* 2. 配置字节:bit5=0(启用 AUX 时钟) bit1=1(启用 AUX IRQ) bit6=1(翻译) */
|
||
{
|
||
int cfg_ok = 0;
|
||
u8 cfg = 0;
|
||
if (ps2_cmd(PS2_CMD_READ_CFG) == 0 && ps2_wait_read() == 0) {
|
||
cfg = inb(PS2_DATA);
|
||
cfg &= (u8)~0x20u;
|
||
cfg |= 0x02u | 0x40u;
|
||
if (ps2_cmd(PS2_CMD_WRITE_CFG) == 0 && ps2_wait_write() == 0) {
|
||
outb(PS2_DATA, cfg);
|
||
cfg_ok = 1;
|
||
}
|
||
}
|
||
if (cfg_ok) logh("[mouseInit] cfg byte=", cfg);
|
||
else logl("[mouseInit] cfg byte access failed, continue");
|
||
}
|
||
|
||
/* 3. 启用 AUX 端口 */
|
||
if (ps2_cmd(PS2_CMD_ENABLE_AUX) != 0) logl("[mouseInit] enable AUX timeout, continue");
|
||
|
||
/* 4. 复位鼠标:0xFF → 再 drain 最多 8 字节(ACK / 自检 0xAA / ID 0x00) */
|
||
{
|
||
int rc = mouse_cmd(MOUSE_CMD_RESET);
|
||
if (rc != 0) logh("[mouseInit] reset rc=", (u64)(i64)rc);
|
||
for (int i = 0; i < 8; i++) {
|
||
if (ps2_wait_read() != 0) break;
|
||
inb(PS2_DATA);
|
||
}
|
||
}
|
||
|
||
/* 5-8. Set Defaults → 采样率 100 → 分辨率 2 → 启用数据报告,逐步验 ACK */
|
||
{
|
||
int rc;
|
||
rc = mouse_cmd(MOUSE_CMD_DEFAULTS);
|
||
if (rc != 0) logh("[mouseInit] set defaults rc=", (u64)(i64)rc);
|
||
rc = mouse_cmd_param(MOUSE_CMD_RATE, 100);
|
||
if (rc != 0) logh("[mouseInit] sample rate rc=", (u64)(i64)rc);
|
||
rc = mouse_cmd_param(MOUSE_CMD_RESOL, 2);
|
||
if (rc != 0) logh("[mouseInit] resolution rc=", (u64)(i64)rc);
|
||
rc = mouse_cmd(MOUSE_CMD_ENABLE);
|
||
if (rc != 0) logh("[mouseInit] enable reporting rc=", (u64)(i64)rc);
|
||
else logl("[mouseInit] data reporting enabled");
|
||
}
|
||
|
||
/* 共享状态初始化:边界取自 boot context 的 framebuffer 尺寸 */
|
||
mouse_state_reset(ctx ? ctx->framebuffer_width : 0,
|
||
ctx ? ctx->framebuffer_height : 0);
|
||
|
||
/* 注册 IRQ12 handler:仅注册,按安全不变式不解屏蔽 PIC、不 sti
|
||
* (见文件顶部“handler 存活性结论”)。 */
|
||
{
|
||
const dkm_kernel_api *api = (ctx && (ctx->flags & DSK_BOOT_FLAG_DKM_READY))
|
||
? (const dkm_kernel_api *)(usize)ctx->dkm_kernel_api : 0;
|
||
if (api && api->irq_register) {
|
||
int rc = api->irq_register(IRQ_MOUSE, (void *)mouse_irq12_handler);
|
||
if (rc == 0) logl("[mouseInit] IRQ12 handler registered (PIC line kept masked)");
|
||
else logh("[mouseInit] irq_register rc=", (u64)(i64)rc);
|
||
} else {
|
||
logl("[mouseInit] kernel api unavailable, IRQ12 not registered");
|
||
}
|
||
}
|
||
|
||
logl("[mouseInit] init complete");
|
||
return;
|
||
}
|