Files
MeowOS-SDK/sdk/meow_ui_time.c
T
AskaEth 27a3750e36 SDK: 同步主仓 M15/M16/bug3/MD3 修复 — 时钟 ABI 补全/IME 冷却窗/列表行高派生/形状 token/翻页键
公开 SDK 落后主仓的一批 bugfix(主仓是固件构建源,这些修复已在设备上跑过)。
本次只做「主仓 → 公开仓」单向同步,图像栈方向由主仓侧另一单处理。

时钟 ABI(M16 第二弹,原文件自认「残余未修」):
- meow_ui_time.c 147→345 行:futex 相对超时接管(__timedwait/_cp)、
  timerfd_create/settime/gettime 的 itimerspec 64↔32 双向转换、
  __clock_settime64 越界返 ENOTSUP,另有 4 个纯换算函数
- meow_ui_internal.h:struct meow_time32 / meow_itimerspec32 契约移入内部头
- CLOCK_ABI.md:补接管机制、验证配方与 objdump 证据
- 新增 test_meow_time64.c

IME bug3(成对才有意义):
- meow_ui_ime.c:IME_TAP_COOLDOWN_MS 200 冷却窗 + 打开时二次 drain
- meow_ui_input.c:meow_input_touch_reset() 并在 drain 内调用(复位触摸状态机)
- test_ime_fix.c:新增用例④ stray-tap

列表行高(M15,修用户可见 bug):
- meow_ui_widget.c:listv_item_h() 由主题字体行高派生,不再写死 56
  (两行列表 62 > 56 会让首行标题被裁剪守卫整条跳过)
- include/meow_ui_widgets.h:新增 meow_listview_item_h()
- 新增 test_meow_ui_listview_rowh.c

其余:meow_select.c / meow_ui_dialog.c 统一 MD3 形状 token;
include/meow_ipc.h 补 MSG_KEY_PAGEUP/PAGEDOWN 枚举;
meow_ui_sys.c / meow_cachegov.c 取时统一走 meow_ui_time_ms()。

测试辅助函数标定:tuku/test/test_settings_lock_host.c 的注入前等待 150ms→350ms。
该等待按修复前 IME 标定,新冷却窗下首键 dt_open≈114ms 会被当迟到抬起吞掉,
只录到 "68" 两位。仅改等待时长,断言原样保留。

验证:tuku 四套脚本全绿(run_tests 644 PASS / zip 106 / cbz 296 / webtoon PASS);
12 个源文件与主仓逐字节一致;sdk/build_sdk.sh 34 成员;
tuku/build_tuku.sh 产出静态 MIPS32r2 ELF。
2026-11-06 21:31:54 +08:00

346 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* meow_ui_time.c — SDK 单调时钟(meow_ui_time_ms)+ mipsel o32 时间 ABI 兜底。
*
* 根因(M11;真机日志 + 反汇编证据链见 sdk/CLOCK_ABI.md):
* zig 0.17 的 clang 前端给 32 位 mips 目标预定义了 __ILP32__;zig 自带 musl 的
* time64 回退分支统一写作 `#if defined(SYS_x_time64) && !defined(__ILP32__)`
* (该 `!__ILP32__` 本意只排除 x32),于是 mipsel 的 libc 把「time64 入口」编译成了
* 老 32 位实现:__clock_gettime64 直接做 SYS_clock_gettime(4263),只写 8 字节
* `{ int32 tv_sec; int32 tv_nsec; }`。而 zig 头文件又无条件把 time_t 声明成 64 位
* (struct timespec 16 字节、tv_sec 8 字节),两边 ABI 不一致:
* tv_sec 低字 = 真 sec,高字 = 真 nsec → 64 位结果高 32 位随机、非单调;
* tv_nsec(结构偏移 8)根本没被写 → 毫秒位恒 0(真机实测全是 1000 的倍数)。
* 容器旧链(gcc)不定义 __ILP32__,time64 回退正确——对照 patch_v2/ncm_app/ncm:
* 同一函数里先 SYS_clock_gettime64(4403)、ENOSYS 后走 4263 并做宽度转换。
*
* 修复:不依赖 libc 头声明的 struct timespec 宽度,直接按**内核 o32 ABI**
* (8 字节 time32:int32 sec + int32 nsec)取时,再自行升成 64 位毫秒;
* 同时在本 TU 给出 __clock_gettime / __clock_gettime64 的强定义。应用链接顺序是
* `... meow_ui.a ... -lc`,归档先参与解析,因此链接期整体替换掉 libc 里那份
* ABI 不一致的实现:time()、gettimeofday()、pthread 定时等待的内部取时
* (musl 的 time()/__timedwait_cp 都调 __clock_gettime)随之修正。
* 上层时间位宽仍是 64 位,只有内核取数按 o32 time32;2038 前有效。
*
* 同一句 `!__ILP32__` 还打穿了睡眠:__nanosleep_time64/__clock_nanosleep 走 `#else`
* 把 16 字节 time64 结构直接交给 time32 系统调用,内核读到的 nsec 恒为 0 →
* `usleep(16000)` 立即返回(SDK 里帧节拍/轮询会变忙等空转)。这里一并按 time32
* 转换后重发,恢复亚秒睡眠。
*
* M16(同源第二弹;逐项证据见 CLOCK_ABI.md §6):同一句 `!__ILP32__` 还打穿了别的入口,
* 本文件按风险分档接管:
* - futex 相对超时(`__timedwait_cp`/`__timedwait`):**已接管**。它是进程同步核心,
* 但 musl 原文逻辑可完整复刻(含 __syscall_cp 取消点语义),且是 SDK/app 唯一在用的
* 受害者(meow_ui_audio 的 pthread_cond_timedwait 在亚秒等待下会立即超时)。
* - timerfd_settime/gettime、clock_settime:**已接管**(纯参数转换,无内部状态)。
* - pselect/ppoll/select、getrusage、wait4、clock_adjtime:**未受影响**(musl 回退分支
* 自己就转成 `long[2]`/有宽度判断),本单只补证据,不改代码。
* - sigtimedwait、semtimedop、timer_settime/gettime、utimensat、recvmmsg、mq_timedsend、
* pthread_mutex_timedlock 的 PI 快路径:仍同源受损但 SDK/App 均未使用,风险收益比不划算,
* 修复配方已写进 CLOCK_ABI.md §7 待后续单。
*/
#include <errno.h>
#include <stdint.h>
#include <time.h>
#include "meow_ui.h"
#include "meow_ui_internal.h"
#if defined(__mips__) && !defined(__mips64) && (__SIZEOF_LONG__ == 4)
#define MEOW_TIME32_KERNEL_ABI 1
#include <pthread.h>
#include <sys/syscall.h>
#include <sys/timerfd.h>
#include <unistd.h>
#else
#define MEOW_TIME32_KERNEL_ABI 0
#endif
/* ---- 平台无关的 time64 → 内核 o32 time32 换算(宿主单测直接覆盖)----
* 这几个函数不碰系统调用,只为“结构宽度”这件事提供唯一实现;mips 分支的接管入口
* 全部经它们转换,宿主 test_meow_time64.c 用同一份代码验边界。 */
void meow_time32_rel_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out)
{
if (sec > INT32_MAX) sec = INT32_MAX;
else if (sec < INT32_MIN) sec = INT32_MIN;
out->tv_sec = (int32_t)sec;
out->tv_nsec = (int32_t)nsec;
}
int meow_time32_abs_from_parts(int64_t sec, int64_t nsec, struct meow_time32 *out)
{
if (sec > INT32_MAX || sec < INT32_MIN) return -1;
out->tv_sec = (int32_t)sec;
out->tv_nsec = (int32_t)nsec;
return 0;
}
int meow_time32_itimerspec_from_64(const struct meow_time64_itimerspec *in,
struct meow_itimerspec32 *out)
{
struct meow_itimerspec32 t;
if (meow_time32_abs_from_parts(in->it_interval.tv_sec, in->it_interval.tv_nsec,
&t.it_interval)) return -1;
if (meow_time32_abs_from_parts(in->it_value.tv_sec, in->it_value.tv_nsec,
&t.it_value)) return -1;
*out = t;
return 0;
}
void meow_time64_itimerspec_from_32(const struct meow_itimerspec32 *in,
struct meow_time64_itimerspec *out)
{
out->it_interval.tv_sec = in->it_interval.tv_sec;
out->it_interval.tv_nsec = in->it_interval.tv_nsec;
out->it_value.tv_sec = in->it_value.tv_sec;
out->it_value.tv_nsec = in->it_value.tv_nsec;
}
uint64_t meow_time_ms_from_parts(int64_t sec, int64_t nsec)
{
return (uint64_t)sec * 1000u + (uint64_t)nsec / 1000000u;
}
uint64_t meow_ui_time_ms(void)
{
#if MEOW_TIME32_KERNEL_ABI
struct meow_time32 t;
t.tv_sec = 0;
t.tv_nsec = 0;
if (syscall(SYS_clock_gettime32, CLOCK_MONOTONIC, &t) != 0) return 0;
return meow_time_ms_from_parts(t.tv_sec, t.tv_nsec);
#else
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) return 0;
return meow_time_ms_from_parts((int64_t)ts.tv_sec, (int64_t)ts.tv_nsec);
#endif
}
#if MEOW_TIME32_KERNEL_ABI
static int32_t time32_clamp(int64_t v)
{
if (v > INT32_MAX) return INT32_MAX;
if (v < 0) return 0;
return (int32_t)v;
}
/* ---- libc 取时入口的强定义 ----
* 返回约定与 musl 一致:0 成功 / -1 失败(errno)。
* 说明:<time.h> 在 _REDIR_TIME64 下把标识符 clock_gettime 的引用改名为
* __clock_gettime64,故本 TU 不定义 clock_gettime 这个名字;musl 里它只是
* __clock_gettime 的 weak alias,同一份语义已由下面的强定义接管。 */
int __clock_gettime(clockid_t clk, struct timespec *ts);
int __clock_gettime64(clockid_t clk, struct timespec *ts);
int __clock_gettime(clockid_t clk, struct timespec *ts)
{
struct meow_time32 t;
if (!ts) { errno = EFAULT; return -1; }
t.tv_sec = 0;
t.tv_nsec = 0;
if (syscall(SYS_clock_gettime32, clk, &t) != 0) return -1;
ts->tv_sec = t.tv_sec;
ts->tv_nsec = t.tv_nsec;
return 0;
}
int __clock_gettime64(clockid_t clk, struct timespec *ts)
{
return __clock_gettime(clk, ts);
}
/* ---- 睡眠入口(同源缺陷:亚秒 usleep 变空转)----
* __clock_nanosleep 按 musl 内部约定返回 0 或正 errno;__nanosleep_time64 是
* nanosleep/usleep 的重定向目标,按公共约定返回 0/-1(errno)。 */
int __clock_nanosleep(clockid_t clk, int flags, const struct timespec *req, struct timespec *rem);
int __clock_nanosleep_time64(clockid_t clk, int flags, const struct timespec *req, struct timespec *rem);
int __nanosleep_time64(const struct timespec *req, struct timespec *rem);
int __clock_nanosleep(clockid_t clk, int flags, const struct timespec *req, struct timespec *rem)
{
struct meow_time32 rq, rm;
long r;
if (clk == CLOCK_THREAD_CPUTIME_ID) return EINVAL;
if (!req) return EFAULT;
if (req->tv_nsec < 0 || req->tv_nsec >= 1000000000L) return EINVAL;
rq.tv_sec = time32_clamp(req->tv_sec);
rq.tv_nsec = (int32_t)req->tv_nsec;
rm.tv_sec = 0;
rm.tv_nsec = 0;
if (clk == CLOCK_REALTIME && !flags)
r = syscall(SYS_nanosleep, &rq, rem ? &rm : 0);
else
r = syscall(SYS_clock_nanosleep_time32, clk, flags, &rq, rem ? &rm : 0);
if (r != 0) return errno;
if (rem) {
rem->tv_sec = rm.tv_sec;
rem->tv_nsec = rm.tv_nsec;
}
return 0;
}
int __clock_nanosleep_time64(clockid_t clk, int flags, const struct timespec *req, struct timespec *rem)
{
return __clock_nanosleep(clk, flags, req, rem);
}
int __nanosleep_time64(const struct timespec *req, struct timespec *rem)
{
int r = __clock_nanosleep(CLOCK_REALTIME, 0, req, rem);
if (r) { errno = r; return -1; }
return 0;
}
/* ---- futex 相对超时(__timedwait_cp / __timedwait 强定义)----
* 同源缺陷:musl thread/__timedwait.c 的 __futex4_cp 也在 `#if defined(SYS_futex_time64)
* && !defined(__ILP32__)` 里做 time64,被 __ILP32__ 打穿后走 `#else`:
* __syscall_cp(SYS_futex(4238), addr, op, val, to) ← to 是 16 字节 time64 结构
* 内核按 o32 time32 只读前 8 字节 {int32 sec, int32 nsec}:
* 偏移 0..3 = tv_sec 低字(够用),偏移 4..7 = tv_sec 高字(正常计时恒 0)。
* 于是「还剩 0.4s」被当成 {0,0} → 立刻超时;「剩 2.3s」被当成 2s 整。
* 受害者:pthread_cond_timedwait / sem_timedwait / pthread_join 超时 / aio_suspend
* (SDK 的 meow_ui_audio.c 与 meow_audiod、ncm 播放线程都在用)。
*
* 风险控制:不重写任何同步算法,只把 musl 的 __timedwait_cp 原文搬过来,唯一改动是
* 发系统调用前把相对超时转成 time32;取消点仍走 libc 的 __syscall_cp(ECANCELED/
* EINTR 语义不变)。链接期接管必须同时定义 __timedwait —— 否则 libc 的 __timedwait.o
* 会因 `__timedwait` 未解析被拉入,与本文的 __timedwait_cp 撞成 duplicate symbol
* (ld.lld 实测会报错)。__eintr_valid_flag 给弱定义,真正的强定义在 libc sigaction.o。
*
* 未覆盖:pthread_mutex_timedlock 的 PTHREAD_PRIO_INHERIT 快路径(同文件里的静态
* __futex4,直接调 FUTEX_LOCK_PI);SDK/App 未使用 PI 互斥量,见 CLOCK_ABI.md §7。 */
#define MEOW_FUTEX_WAIT 0
#define MEOW_FUTEX_PRIVATE 128
int __pthread_setcancelstate(int new, int *old);
long __syscall_cp(long nr, long a, long b, long c, long d, long e, long f);
__attribute__((weak)) volatile int __eintr_valid_flag = 0;
static int meow_futex4_cp(volatile void *addr, int op, int val, const struct timespec *to)
{
struct meow_time32 t32;
void *top = 0;
long r;
if (to) {
meow_time32_rel_from_parts((int64_t)to->tv_sec, (int64_t)to->tv_nsec, &t32);
top = &t32;
}
r = __syscall_cp(SYS_futex, (long)addr, op, val, (long)top, 0, 0);
if (r != -ENOSYS) return (int)r;
return (int)__syscall_cp(SYS_futex, (long)addr,
op & ~MEOW_FUTEX_PRIVATE, val, (long)top, 0, 0);
}
int __timedwait_cp(volatile int *addr, int val, clockid_t clk,
const struct timespec *at, int priv);
int __timedwait(volatile int *addr, int val, clockid_t clk,
const struct timespec *at, int priv);
int __timedwait_cp(volatile int *addr, int val, clockid_t clk,
const struct timespec *at, int priv)
{
int r;
struct timespec to, *top = 0;
if (priv) priv = MEOW_FUTEX_PRIVATE;
if (at) {
/* musl 原文是 `>= 1000000000UL`(负值按无符号也判 EINVAL);显式判负以保 -Wextra 0 warning */
if (at->tv_nsec < 0 || at->tv_nsec >= 1000000000L) return EINVAL;
if (__clock_gettime(clk, &to)) return EINVAL;
to.tv_sec = at->tv_sec - to.tv_sec;
if ((to.tv_nsec = at->tv_nsec - to.tv_nsec) < 0) {
to.tv_sec--;
to.tv_nsec += 1000000000;
}
if (to.tv_sec < 0) return ETIMEDOUT;
top = &to;
}
r = -meow_futex4_cp(addr, MEOW_FUTEX_WAIT | priv, val, top);
if (r != EINTR && r != ETIMEDOUT && r != ECANCELED) r = 0;
if (r == EINTR && !__eintr_valid_flag) r = 0;
return r;
}
int __timedwait(volatile int *addr, int val, clockid_t clk,
const struct timespec *at, int priv)
{
int cs, r;
__pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
r = __timedwait_cp(addr, val, clk, at, priv);
__pthread_setcancelstate(cs, 0);
return r;
}
/* ---- timerfd(musl src/linux/timerfd.c 同款缺陷)----
* 回退分支把 32 字节 time64 `struct itimerspec` 直接交给内核;o32 内核只认
* 16 字节 {interval.sec, interval.nsec, value.sec, value.nsec}。实际读到的
* value.sec = 调用者 interval.tv_nsec(错位),value.nsec = 结构填充(垃圾);
* gettime 反向把内核的前 16 字节写进错位的字段。SDK/App 当前未用 timerfd,
* 但成本极低,顺手按内核 ABI 转换。三个符号必须同一 TU 全定义:libc 的
* timerfd.o 同时提供 create/settime/gettime,缺一个就会把它拉进来导致重复定义。 */
int timerfd_create(int clockid, int flags)
{
return (int)syscall(SYS_timerfd_create, clockid, flags);
}
int __timerfd_settime64(int fd, int flags, const struct itimerspec *new,
struct itimerspec *old);
int __timerfd_gettime64(int fd, struct itimerspec *cur);
int __timerfd_settime64(int fd, int flags, const struct itimerspec *new,
struct itimerspec *old)
{
struct meow_time64_itimerspec in;
struct meow_itimerspec32 n32, o32;
if (!new) { errno = EFAULT; return -1; }
in.it_interval.tv_sec = new->it_interval.tv_sec;
in.it_interval.tv_nsec = new->it_interval.tv_nsec;
in.it_value.tv_sec = new->it_value.tv_sec;
in.it_value.tv_nsec = new->it_value.tv_nsec;
if (meow_time32_itimerspec_from_64(&in, &n32)) { errno = ENOTSUP; return -1; }
if (syscall(SYS_timerfd_settime32, fd, flags, &n32, old ? &o32 : 0) != 0)
return -1;
if (old) {
old->it_interval.tv_sec = o32.it_interval.tv_sec;
old->it_interval.tv_nsec = o32.it_interval.tv_nsec;
old->it_value.tv_sec = o32.it_value.tv_sec;
old->it_value.tv_nsec = o32.it_value.tv_nsec;
}
return 0;
}
int __timerfd_gettime64(int fd, struct itimerspec *cur)
{
struct meow_itimerspec32 c32;
if (!cur) { errno = EFAULT; return -1; }
if (syscall(SYS_timerfd_gettime32, fd, &c32) != 0) return -1;
cur->it_interval.tv_sec = c32.it_interval.tv_sec;
cur->it_interval.tv_nsec = c32.it_interval.tv_nsec;
cur->it_value.tv_sec = c32.it_value.tv_sec;
cur->it_value.tv_nsec = c32.it_value.tv_nsec;
return 0;
}
/* ---- clock_settime(musl src/time/clock_settime.c 同款缺陷)----
* 回退分支把 16 字节 time64 结构直接交给 SYS_clock_settime32;内核只写/读 8 字节,
* nsec 位永远为 0。绝对时刻超出 int32 时回 ENOTSUP(与 musl time64 路径一致)。 */
int __clock_settime64(clockid_t clk, const struct timespec *ts)
{
struct meow_time32 t;
if (!ts) { errno = EFAULT; return -1; }
if (meow_time32_abs_from_parts((int64_t)ts->tv_sec, (int64_t)ts->tv_nsec, &t)) {
errno = ENOTSUP;
return -1;
}
if (syscall(SYS_clock_settime32, clk, &t) != 0) return -1;
return 0;
}
#endif