公开 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。
346 lines
15 KiB
C
346 lines
15 KiB
C
/* 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
|