feat: Linux ABI P0-P6, BMIDE DMA, ImageSee, 4KiB page pool and Explorer hardening
This commit is contained in:
1 parent
ebd43ef9da
commit
6f856992da
41 files changed
+8697
-2471
No files matched your search
+5
-1
@@ -97,4 +97,8 @@ _UNCADK/
|
||||
# 编译
|
||||
**/output/
|
||||
**/temp/
|
||||
**/__vpcbuild__/
|
||||
**/__vpcbuild__/
|
||||
# 本地大文件 / 临时文件
|
||||
Vmware/
|
||||
NotoSansSC/
|
||||
1.py
|
||||
+236
-47
@@ -5,6 +5,9 @@ import vpsdk.process as process
|
||||
import vpsdk.syscall as syscall
|
||||
import vpsdk.vpui as vpui # 复用它的 8x16 点阵字体表
|
||||
import vpsdk.fonttools.fonttools as fonttools
|
||||
import vpsdk.imagesee.imagesee as imagesee
|
||||
import vpsdk.imagesee.image as image
|
||||
import vpsdk.stdlib as stdlib
|
||||
from stdint import *
|
||||
import string
|
||||
|
||||
@@ -55,6 +58,10 @@ define MIN_WIN_W = 160
|
||||
define MIN_WIN_H = 100
|
||||
# 窗口阴影(0 = 关闭)
|
||||
define SHADOW_ENABLED = 1
|
||||
# 画面同步开关:
|
||||
# 1 = 每帧合成前等待垂直回扫(减少撕裂,VMware 下可能受 0x3DA 可用性影响)
|
||||
# 0 = 不等回扫,最低输入延迟(拖动最跟手,但可能出现撕裂)
|
||||
define SCREEN_SYNC = 1
|
||||
define SHADOW_LEFT = 8
|
||||
define SHADOW_RIGHT = 12
|
||||
define SHADOW_TOP = 4
|
||||
@@ -262,6 +269,35 @@ def _corner_alpha(px: int, py: int, cx: int, cy: int, r: int) -> int:
|
||||
return 255
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 把已合成的一整段像素提交到 framebuffer。
|
||||
#
|
||||
# 用 non-temporal store(movnti)+ sfence:即使 framebuffer 是回写映射,
|
||||
# 也绕过数据缓存直接写到内存,扫描输出能立刻看到;同时 8 字节顺序写会合并
|
||||
# 成写合并缓冲,比逐字节/逐像素写快得多。QEMU TCG 下等价于普通写,不受影响。
|
||||
# ---------------------------------------------------------------------------
|
||||
def _fb_copy(dst: uint64, src: uint64, nbytes: uint64):
|
||||
if nbytes == 0: return
|
||||
if (nbytes & uint64(7)) == 0:
|
||||
qwords: uint64 = nbytes >> 3
|
||||
__asm__(f"""mov rdi, {asminp(dst, ASM_DESCR.REG_ANY)}
|
||||
mov rsi, {asminp(src, ASM_DESCR.REG_ANY)}
|
||||
mov rcx, {asminp(qwords, ASM_DESCR.REG_ANY)}
|
||||
Lfbcopy:
|
||||
mov rax, [rsi]
|
||||
movnti qword ptr [rdi], rax
|
||||
add rsi, 8
|
||||
add rdi, 8
|
||||
dec rcx
|
||||
jnz Lfbcopy
|
||||
sfence""",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX,
|
||||
ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDI,
|
||||
ASM_DESCR.CLOBBER_RSI])
|
||||
else:
|
||||
string.memcpy(rawptr(dst), rawptr(src), nbytes)
|
||||
|
||||
|
||||
class DeskServer:
|
||||
# 屏幕
|
||||
fb: uint32* # /dev/fb0 映射后的线性地址
|
||||
@@ -271,6 +307,7 @@ class DeskServer:
|
||||
pitch_px: int
|
||||
fmt: uint32 # 0 = RGBA, 1 = BGRA
|
||||
bg_row: uint32* # 一行背景的预构造副本
|
||||
bg_full: uint32* # 壁纸(整屏);None = 使用 bg_row 纯色背景
|
||||
back: uint32* # 后备缓冲:绘制先落这里,整帧后逐行拷到 fb
|
||||
# 共享区
|
||||
shm: uint64
|
||||
@@ -310,6 +347,14 @@ class DeskServer:
|
||||
resize_oh: int
|
||||
resize_sx: int
|
||||
resize_sy: int
|
||||
# 内容双缓冲:vis_* 是当前正在合成的内容;s.fb_off/fb_w/fb_h 是应用正在
|
||||
# 绘制的新内容。应用 FLUSH 后才交换,避免 resize 过程中按新步长读旧内容。
|
||||
vis_off: [desk.DESK_MAX_WIN]uint32
|
||||
vis_cap: [desk.DESK_MAX_WIN]uint32
|
||||
vis_w: [desk.DESK_MAX_WIN]int
|
||||
vis_h: [desk.DESK_MAX_WIN]int
|
||||
spare_off: [desk.DESK_MAX_WIN]uint32
|
||||
spare_cap: [desk.DESK_MAX_WIN]uint32
|
||||
# 标题栏按钮的 hover 状态(用于变色和刷新)
|
||||
hover_btn_win: int
|
||||
hover_btn_kind: int
|
||||
@@ -323,6 +368,79 @@ class DeskServer:
|
||||
return (uint32(255) << 24) | (r << 16) | (g << 8) | b
|
||||
return (uint32(255) << 24) | (b << 16) | (g << 8) | r
|
||||
|
||||
# 桌面壁纸:/sys/images/screen/background.jpg,等比“ cover”裁切铺满屏幕。
|
||||
# 加载失败时保持 init() 里预构造的纯色 bg_row,不影响启动。
|
||||
def _load_background(self):
|
||||
img: image.Image* = imagesee.load("/sys/images/screen/background.jpg")
|
||||
if img is None:
|
||||
# FAT32 is shared with other booting apps; retry once before
|
||||
# falling back to the solid color.
|
||||
process.yield_cpu()
|
||||
img = imagesee.load("/sys/images/screen/background.jpg")
|
||||
if img is None:
|
||||
devfs.serial_puts("[explorer] background: not found (solid color)\n")
|
||||
return
|
||||
w: int = self.fb_w
|
||||
h: int = self.fb_h
|
||||
if w <= 0 or h <= 0: return
|
||||
buf: uint32* = uint32*(stdlib.malloc(uint64(w * h * 4)))
|
||||
if buf is None: return
|
||||
iw: int = img.width
|
||||
ih: int = img.height
|
||||
if iw <= 0 or ih <= 0: return
|
||||
sw: int = iw
|
||||
sh: int = ih
|
||||
ox: int = 0
|
||||
oy: int = 0
|
||||
if iw * h > ih * w:
|
||||
# 图片更宽:按高度铺满,左右裁切
|
||||
sw = (ih * w) / h
|
||||
if sw < 1: sw = 1
|
||||
ox = (iw - sw) / 2
|
||||
else:
|
||||
sh = (iw * h) / w
|
||||
if sh < 1: sh = 1
|
||||
oy = (ih - sh) / 2
|
||||
xoff: int32* = int32*(stdlib.malloc(uint64(w * 4)))
|
||||
if xoff is None: return
|
||||
x: int = 0
|
||||
while x < w:
|
||||
sx: int = ox + (x * sw) / w
|
||||
if sx < 0: sx = 0
|
||||
if sx >= iw: sx = iw - 1
|
||||
xoff[x] = int32(sx)
|
||||
x = x + 1
|
||||
y: int = 0
|
||||
while y < h:
|
||||
sy: int = oy + (y * sh) / h
|
||||
if sy < 0: sy = 0
|
||||
if sy >= ih: sy = ih - 1
|
||||
srow: int = sy * iw
|
||||
drow: int = y * w
|
||||
if self.fmt == uint32(1):
|
||||
# ImageSee 的 0xAARRGGBB 与 BGRA framebuffer 内存布局一致
|
||||
x = 0
|
||||
while x < w:
|
||||
buf[drow + x] = img.pixels[srow + int(xoff[x])] | uint32(0xFF000000)
|
||||
x = x + 1
|
||||
else:
|
||||
x = 0
|
||||
while x < w:
|
||||
c: uint32 = img.pixels[srow + int(xoff[x])]
|
||||
r: uint32 = (c >> 16) & uint32(0xFF)
|
||||
g: uint32 = (c >> 8) & uint32(0xFF)
|
||||
b: uint32 = c & uint32(0xFF)
|
||||
buf[drow + x] = (uint32(0xFF) << 24) | (b << 16) | (g << 8) | r
|
||||
x = x + 1
|
||||
y = y + 1
|
||||
self.bg_full = buf
|
||||
devfs.serial_puts("[explorer] background: image loaded\n")
|
||||
|
||||
def bg_src(self, y: int) -> uint32*:
|
||||
if self.bg_full is not None:
|
||||
return uint32*(uint64(self.bg_full) + uint64(y * self.fb_w) * uint64(4))
|
||||
return self.bg_row
|
||||
|
||||
# ---- 按 alpha/255 把 fg 混到 bg 上(RGBA/BGRA 同为 R/B@0,16、G@8,可共用 SWAR)----
|
||||
def _blend(self, bg: uint32, fg: uint32, a: int) -> uint32:
|
||||
ia: int = 255 - a
|
||||
@@ -370,6 +488,14 @@ class DeskServer:
|
||||
self.resize_oh = 0
|
||||
self.resize_sx = 0
|
||||
self.resize_sy = 0
|
||||
i0: int = 0
|
||||
for i0 in range(desk.DESK_MAX_WIN):
|
||||
self.vis_off[i0] = uint32(0)
|
||||
self.vis_cap[i0] = uint32(0)
|
||||
self.vis_w[i0] = 0
|
||||
self.vis_h[i0] = 0
|
||||
self.spare_off[i0] = uint32(0)
|
||||
self.spare_cap[i0] = uint32(0)
|
||||
self.hover_btn_win = -1
|
||||
self.hover_btn_kind = 0
|
||||
# 预构造一行背景,渲染时整行 memcpy
|
||||
@@ -380,6 +506,9 @@ class DeskServer:
|
||||
col: uint32 = self.rgb(uint32(25), uint32(25), uint32(35))
|
||||
for i in range(w):
|
||||
self.bg_row[i] = col
|
||||
# 可选壁纸:整屏缓冲,失败则继续用上面的纯色行
|
||||
self.bg_full = None
|
||||
self._load_background()
|
||||
# 后备缓冲:整屏像素,行距 = fb_w(与 framebuffer 的 pitch 无关)
|
||||
back_va: uint64 = syscall.mmap(0, uint64(w) * uint64(h) * uint64(4),
|
||||
uint64(syscall.PROT_READ | syscall.PROT_WRITE),
|
||||
@@ -517,24 +646,41 @@ class DeskServer:
|
||||
|
||||
# 调整窗口外框几何。像素缓冲只在容量不足时扩容:arena 是 bump 分配,
|
||||
# 容量内复用可避免反复最大化/还原把 arena 耗尽。返回 1 成功 / 0 失败。
|
||||
def _set_window_rect(self, s: desk.WinSlot*, x: int, y: int, w: int, h: int) -> int:
|
||||
def _set_window_rect(self, s: desk.WinSlot*, win: int, x: int, y: int, w: int, h: int) -> int:
|
||||
ch: int = self._content_h(h)
|
||||
need: int = w * ch
|
||||
if w <= 0 or need <= 0: return 0
|
||||
if need > int(s.fb_cap):
|
||||
# 交互缩放时按 1.5 倍增长,避免拖拽过程中每加几像素就切一块新 arena;
|
||||
# 最大化的一次性大跨度仍会直接按 need 分配。
|
||||
alloc_px: int = need
|
||||
grow: int = int(s.fb_cap) + int(s.fb_cap) / 2
|
||||
if grow > alloc_px: alloc_px = grow
|
||||
off: uint32 = desk.desk_arena_alloc(self.shm, uint32(alloc_px * 4))
|
||||
if off == uint32(0): return 0
|
||||
s.fb_off = off
|
||||
s.fb_cap = uint32(alloc_px)
|
||||
b: uint32* = uint32*(self.shm + uint64(off))
|
||||
col: uint32 = self.rgb(uint32(30), uint32(30), uint32(50))
|
||||
for k in range(alloc_px):
|
||||
b[k] = col
|
||||
# 纯移动:内容尺寸没变,不切缓冲
|
||||
if w == int(s.fb_w) and ch == int(s.fb_h):
|
||||
s.ux = x
|
||||
s.uy = y
|
||||
s.w = w
|
||||
s.h = h
|
||||
return 1
|
||||
target_off: uint32 = s.fb_off
|
||||
target_cap: uint32 = s.fb_cap
|
||||
# 当前绘制目标若正在显示(上次已 FLUSH),或容量不足,就换到备用/新缓冲。
|
||||
# 这样 resize 期间合成器仍读旧 vis 缓冲,绝不会按新步长读旧内容。
|
||||
if target_off == self.vis_off[win] or need > int(target_cap):
|
||||
if self.spare_off[win] != uint32(0) and need <= int(self.spare_cap[win]):
|
||||
target_off = self.spare_off[win]
|
||||
target_cap = self.spare_cap[win]
|
||||
self.spare_off[win] = uint32(0)
|
||||
self.spare_cap[win] = uint32(0)
|
||||
else:
|
||||
alloc_px: int = need
|
||||
grow: int = int(target_cap) + int(target_cap) / 2
|
||||
if grow > alloc_px: alloc_px = grow
|
||||
off: uint32 = desk.desk_arena_alloc(self.shm, uint32(alloc_px * 4))
|
||||
if off == uint32(0): return 0
|
||||
# 旧 target 还没显示过,可留作下一次 resize 的备用
|
||||
if s.fb_off != self.vis_off[win] and s.fb_off != uint32(0):
|
||||
self.spare_off[win] = s.fb_off
|
||||
self.spare_cap[win] = s.fb_cap
|
||||
target_off = off
|
||||
target_cap = uint32(alloc_px)
|
||||
s.fb_off = target_off
|
||||
s.fb_cap = target_cap
|
||||
s.ux = x
|
||||
s.uy = y
|
||||
s.w = w
|
||||
@@ -577,7 +723,7 @@ class DeskServer:
|
||||
if ny + nh > self.fb_h: nh = self.fb_h - ny
|
||||
if nx == s.ux and ny == s.uy and nw == s.w and nh == s.h: return
|
||||
self.mark_win(s.uy, s.h)
|
||||
if self._set_window_rect(s, nx, ny, nw, nh) != 0:
|
||||
if self._set_window_rect(s, self.resize_win, nx, ny, nw, nh) != 0:
|
||||
self.push_event(self.resize_win, uint32(desk.DESK_EVT_RESIZE),
|
||||
int(s.fb_w), int(s.fb_h), 0, 0)
|
||||
self.mark_win(s.uy, s.h)
|
||||
@@ -635,7 +781,7 @@ class DeskServer:
|
||||
mw: int = self.fb_w
|
||||
mh: int = self.fb_h - TASKBAR_H
|
||||
if mh < TITLE_H + TITLE_GAP + 1: return
|
||||
if self._set_window_rect(s, 0, 0, mw, mh) == 0: return
|
||||
if self._set_window_rect(s, win, 0, 0, mw, mh) == 0: return
|
||||
s.restore_x = ox
|
||||
s.restore_y = oy
|
||||
s.restore_w = ow
|
||||
@@ -652,7 +798,7 @@ class DeskServer:
|
||||
if s.used == 0 or s.maximized == 0: return 0
|
||||
oy: int = s.uy
|
||||
oh: int = s.h
|
||||
if self._set_window_rect(s, s.restore_x, s.restore_y, s.restore_w, s.restore_h) == 0:
|
||||
if self._set_window_rect(s, win, s.restore_x, s.restore_y, s.restore_w, s.restore_h) == 0:
|
||||
return 0
|
||||
s.maximized = 0
|
||||
self.mark_win(oy, oh)
|
||||
@@ -727,6 +873,12 @@ class DeskServer:
|
||||
s.fb_cap = uint32(cap_px)
|
||||
s.fb_w = uint32(w)
|
||||
s.fb_h = uint32(content_h)
|
||||
self.vis_off[i] = buf_off
|
||||
self.vis_cap[i] = uint32(cap_px)
|
||||
self.vis_w[i] = w
|
||||
self.vis_h[i] = content_h
|
||||
self.spare_off[i] = uint32(0)
|
||||
self.spare_cap[i] = uint32(0)
|
||||
string.memset(&s.title, 0, desk.DESK_TITLE_MAX)
|
||||
# 像素缓冲先清成窗口底色,避免 app 还没画就露出垃圾
|
||||
buf: uint32* = uint32*(self.shm + uint64(buf_off))
|
||||
@@ -986,8 +1138,13 @@ class DeskServer:
|
||||
content_row: int = yy - content_y0
|
||||
if row_is_title != 0 and title_alpha < 255:
|
||||
self._blend_row(wx + 1, yy, ww - 2, row_col, title_alpha)
|
||||
elif rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh:
|
||||
self.row_blit(wx + 1, yy, buf, content_row * fw + 1, ww - 2)
|
||||
elif rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh and buf is not None:
|
||||
blit_w: int = ww - 2
|
||||
if blit_w > fw - 2: blit_w = fw - 2
|
||||
if blit_w > 0:
|
||||
self.row_blit(wx + 1, yy, buf, content_row * fw + 1, blit_w)
|
||||
if blit_w < ww - 2:
|
||||
self.row_fill(wx + 1 + blit_w, yy, (ww - 2) - blit_w, row_col)
|
||||
else:
|
||||
self.row_fill(wx, yy, ww, row_col)
|
||||
if rel == 0 or rel == wh - 1:
|
||||
@@ -1005,7 +1162,7 @@ class DeskServer:
|
||||
wh: int = s.h
|
||||
content_row: int = yy - content_y0
|
||||
do_blit: int = 0
|
||||
if rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh:
|
||||
if rel >= TITLE_H + TITLE_GAP and rel < wh - 1 and content_row >= 0 and content_row < fh and buf is not None:
|
||||
do_blit = 1
|
||||
cyc: int = r
|
||||
if rel >= r: cyc = wh - r - 1
|
||||
@@ -1026,7 +1183,7 @@ class DeskServer:
|
||||
if _is_in_corner(rx, rel, cxc, cyc, r) != 0:
|
||||
on_border: int = _is_on_border(rx, rel, ww, wh, r)
|
||||
idx: int = yy * self.fb_w + px
|
||||
if on_border == 0 and do_blit != 0:
|
||||
if on_border == 0 and do_blit != 0 and rx < fw:
|
||||
self.back[idx] = buf[content_row * fw + rx]
|
||||
px += 1
|
||||
continue
|
||||
@@ -1052,7 +1209,12 @@ class DeskServer:
|
||||
if rel == 0 or rel == wh - 1:
|
||||
self.row_fill(mid0, yy, midw, outline_col)
|
||||
elif do_blit != 0:
|
||||
self.row_blit(mid0, yy, buf, content_row * fw + r, midw)
|
||||
mid_blit_w: int = midw
|
||||
if mid_blit_w > fw - r: mid_blit_w = fw - r
|
||||
if mid_blit_w > 0:
|
||||
self.row_blit(mid0, yy, buf, content_row * fw + r, mid_blit_w)
|
||||
if mid_blit_w < midw:
|
||||
self.row_fill(mid0 + mid_blit_w, yy, midw - mid_blit_w, row_col)
|
||||
elif row_is_title != 0 and title_alpha < 255:
|
||||
self._blend_row(mid0, yy, midw, row_col, title_alpha)
|
||||
else:
|
||||
@@ -1069,7 +1231,7 @@ class DeskServer:
|
||||
if _is_in_corner(rx, rel, cxr, cyc, r) != 0:
|
||||
on_border: int = _is_on_border(rx, rel, ww, wh, r)
|
||||
idx: int = yy * self.fb_w + px
|
||||
if on_border == 0 and do_blit != 0:
|
||||
if on_border == 0 and do_blit != 0 and rx < fw:
|
||||
self.back[idx] = buf[content_row * fw + rx]
|
||||
px += 1
|
||||
continue
|
||||
@@ -1446,7 +1608,7 @@ class DeskServer:
|
||||
w: int = order[i]
|
||||
if yy < sy[w] or yy >= sy[w] + sh[w]: continue
|
||||
if sx[w] > xx:
|
||||
self.row_blit(xx, yy, self.bg_row, xx, sx[w] - xx)
|
||||
self.row_blit(xx, yy, self.bg_src(yy), xx, sx[w] - xx)
|
||||
ex: int = sx[w] + sw[w]
|
||||
if ex > xx: xx = ex
|
||||
if smode[w] != 0:
|
||||
@@ -1457,9 +1619,9 @@ class DeskServer:
|
||||
elif rel < WIN_RADIUS or rel >= sh[w] - WIN_RADIUS:
|
||||
need = 1
|
||||
if need != 0:
|
||||
self.row_blit(sx[w], yy, self.bg_row, sx[w], sw[w])
|
||||
self.row_blit(sx[w], yy, self.bg_src(yy), sx[w], sw[w])
|
||||
if xx < self.fb_w:
|
||||
self.row_blit(xx, yy, self.bg_row, xx, self.fb_w - xx)
|
||||
self.row_blit(xx, yy, self.bg_src(yy), xx, self.fb_w - xx)
|
||||
yy += 1
|
||||
# 2) 窗口
|
||||
for i in range(cnt):
|
||||
@@ -1546,13 +1708,22 @@ class DeskServer:
|
||||
self._draw_shadow(s, y0, y1, shadow_r)
|
||||
if wy + wh <= y0 or wy >= y1: return
|
||||
|
||||
buf: uint32* = uint32*(self.shm + uint64(s.fb_off))
|
||||
fw: int = int(s.fb_w)
|
||||
fh: int = int(s.fb_h)
|
||||
# 槽表被写坏时宁可跳过该窗口,也不要按越界 fb_off 读像素把整个合成器带崩。
|
||||
need_bytes: uint64 = uint64(fw) * uint64(fh) * uint64(4)
|
||||
if s.fb_off < uint32(desk.DESK_OFF_ARENA) or uint64(s.fb_off) + need_bytes > uint64(desk.DESK_SHM_SIZE):
|
||||
return
|
||||
# 合成器只读“最后一次 FLUSH 的可见缓冲”。resize 期间 s.fb_* 已经是新尺寸,
|
||||
# 但应用还没画完,绝不能拿新步长去读旧缓冲(否则就是截图里的乱码)。
|
||||
v_off: uint32 = self.vis_off[win]
|
||||
fw: int = self.vis_w[win]
|
||||
fh: int = self.vis_h[win]
|
||||
buf: uint32* = None
|
||||
if v_off >= uint32(desk.DESK_OFF_ARENA) and fw > 0 and fh > 0:
|
||||
need_bytes: uint64 = uint64(fw) * uint64(fh) * uint64(4)
|
||||
if uint64(v_off) + need_bytes <= uint64(desk.DESK_SHM_SIZE):
|
||||
buf = uint32*(self.shm + uint64(v_off))
|
||||
else:
|
||||
fw = 0
|
||||
fh = 0
|
||||
else:
|
||||
fw = 0
|
||||
fh = 0
|
||||
content_y0: int = wy + th + TITLE_GAP
|
||||
r: int = WIN_RADIUS
|
||||
yy: int = y0
|
||||
@@ -1667,10 +1838,13 @@ class DeskServer:
|
||||
|
||||
def render(self):
|
||||
if self.dirty_any == 0: return
|
||||
devfs.fb_wait_vblank(self.fb_fd)
|
||||
# 逐脏段渲染并立刻提交。**不要**合并成"最小脏行到最大脏行"的一整段:窗口在顶部、
|
||||
# 任务栏在底部时那会退化成每帧全屏渲染 + 全屏提交(4MB memcpy),帧率直接崩。
|
||||
# 段内先全画进 back 再逐行提交,保证每一行提交出去的都是完整一帧的内容。
|
||||
if SCREEN_SYNC != 0:
|
||||
# 可选:等垂直回扫,减少撕裂。内核侧是有界轮询(2048 次),
|
||||
# 不会像旧版 2×100000 次端口读那样拖慢一帧。
|
||||
devfs.fb_wait_vblank(self.fb_fd)
|
||||
# 逐脏段渲染并提交。**不要**合并成"最小脏行到最大脏行"的一整段:窗口在顶部、
|
||||
# 任务栏在底部时那会退化成每帧全屏渲染 + 全屏提交,帧率直接崩。
|
||||
# 段内先全画进 back 再整段提交,保证提交出去的每一行都是完整一帧的内容。
|
||||
y: int = 0
|
||||
while y < self.fb_h:
|
||||
if self.dirty[y] == 0:
|
||||
@@ -1681,14 +1855,19 @@ class DeskServer:
|
||||
y += 1
|
||||
y1: int = y
|
||||
self.render_range(y0, y1)
|
||||
yy: int = y0
|
||||
while yy < y1:
|
||||
if self.dirty[yy] != 0:
|
||||
# fb 行距 = pitch_px,back 行距 = fb_w
|
||||
string.memcpy(rawptr(uint64(self.fb) + uint64(yy * self.pitch_px) * uint64(4)),
|
||||
rawptr(uint64(self.back) + uint64(yy * self.fb_w) * uint64(4)),
|
||||
uint64(self.fb_w) * uint64(4))
|
||||
yy += 1
|
||||
# back 与 fb 行距一致时整段一次 memcpy:大范围拖动时比逐行 1080 次
|
||||
# rep movsb 少很多固定开销;行距不同才退回逐行。
|
||||
if self.pitch_px == self.fb_w:
|
||||
_fb_copy(uint64(self.fb) + uint64(y0 * self.pitch_px) * uint64(4),
|
||||
uint64(self.back) + uint64(y0 * self.fb_w) * uint64(4),
|
||||
uint64((y1 - y0) * self.fb_w) * uint64(4))
|
||||
else:
|
||||
yy: int = y0
|
||||
while yy < y1:
|
||||
_fb_copy(uint64(self.fb) + uint64(yy * self.pitch_px) * uint64(4),
|
||||
uint64(self.back) + uint64(yy * self.fb_w) * uint64(4),
|
||||
uint64(self.fb_w) * uint64(4))
|
||||
yy += 1
|
||||
self.clear_dirty()
|
||||
|
||||
# ---------------- 事件投递 ----------------
|
||||
@@ -1806,7 +1985,7 @@ class DeskServer:
|
||||
self.mark_win(s.uy, s.h)
|
||||
elif ty == uint32(desk.DESK_REQ_RESIZE):
|
||||
self.mark_win(s.uy, s.h)
|
||||
if self._set_window_rect(s, s.ux, s.uy, m.a, m.b) != 0:
|
||||
if self._set_window_rect(s, win, s.ux, s.uy, m.a, m.b) != 0:
|
||||
s.maximized = 0
|
||||
self.mark_win(s.uy, s.h)
|
||||
elif ty == uint32(desk.DESK_REQ_TITLE):
|
||||
@@ -1819,6 +1998,16 @@ class DeskServer:
|
||||
if self.hit_test(self.cx, self.cy) == win:
|
||||
self._mark_cursor_band()
|
||||
elif ty == uint32(desk.DESK_REQ_FLUSH):
|
||||
# 应用已经按 s.fb_w/fb_h 画完一帧:此刻才把"正在显示的内容"切到
|
||||
# 新缓冲。旧的 vis 缓冲留作下一次 resize 的备用。
|
||||
old_off: uint32 = self.vis_off[win]
|
||||
if old_off != s.fb_off:
|
||||
self.spare_off[win] = old_off
|
||||
self.spare_cap[win] = self.vis_cap[win]
|
||||
self.vis_off[win] = s.fb_off
|
||||
self.vis_cap[win] = s.fb_cap
|
||||
self.vis_w[win] = int(s.fb_w)
|
||||
self.vis_h[win] = int(s.fb_h)
|
||||
# 最小化窗口仍在后台绘制/FLUSH:不要置脏,否则合成器每帧都要重画背景。
|
||||
if s.minimized == 0:
|
||||
self.mark(s.uy, s.h)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
ENTRY(__entry)
|
||||
|
||||
SECTIONS {
|
||||
. = 0x400000;
|
||||
|
||||
.text : {
|
||||
__entry = .;
|
||||
*(.text.startup)
|
||||
*(.text)
|
||||
*(.text.*)
|
||||
}
|
||||
|
||||
.rodata : {
|
||||
*(.rodata)
|
||||
*(.rodata.*)
|
||||
}
|
||||
|
||||
.data : {
|
||||
*(.data)
|
||||
*(.data.*)
|
||||
}
|
||||
|
||||
.bss : {
|
||||
__bss_start = .;
|
||||
*(.bss)
|
||||
*(.bss.*)
|
||||
__bss_end = .;
|
||||
}
|
||||
|
||||
/DISCARD/ : {
|
||||
*(.comment)
|
||||
*(.note)
|
||||
*(.eh_frame)
|
||||
*(.eh_frame_hdr)
|
||||
*(.reloc)
|
||||
*(.rela)
|
||||
*(.debug*)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
# ImageSee demo: decode PNG and JPEG with vpsdk.imagesee and show them.
|
||||
|
||||
import vpsdk.vpui as vpui
|
||||
import vpsdk.window as window
|
||||
import vpsdk.process as process
|
||||
import vpsdk.stdlib as stdlib
|
||||
import vpsdk.imagesee.imagesee as imagesee
|
||||
import vpsdk.imagesee.image as im
|
||||
import vpsdk.stdio as stdio
|
||||
import string
|
||||
from stdint import *
|
||||
|
||||
define WIN_X = 40
|
||||
define WIN_Y = 36
|
||||
define WIN_W = 1120
|
||||
define WIN_H = 616
|
||||
define CANVAS_W = 536
|
||||
define CANVAS_H = 520
|
||||
define BG = 0xFF14141E
|
||||
define PANEL = 0xFF20202C
|
||||
define BORDER = 0xFF3A3A50
|
||||
|
||||
# Fill dst[0..w*h) with a dark checkerboard so transparent parts are visible.
|
||||
def _checker(dst: uint32*, w: int, h: int):
|
||||
y: int = 0
|
||||
while y < h:
|
||||
x: int = 0
|
||||
while x < w:
|
||||
c: uint32 = uint32(PANEL)
|
||||
if ((x >> 4) + (y >> 4)) & 1:
|
||||
c = uint32(BG)
|
||||
dst[y * w + x] = c
|
||||
x = x + 1
|
||||
y = y + 1
|
||||
|
||||
def _draw_fit(dst: uint32*, dw: int, dh: int, img: im.Image*,
|
||||
ox: int, oy: int, bw: int, bh: int):
|
||||
if img is None: return
|
||||
tw: int = bw
|
||||
th: int = (tw * img.height) / img.width
|
||||
if th > bh:
|
||||
th = bh
|
||||
tw = (th * img.width) / img.height
|
||||
if tw < 1 or th < 1: return
|
||||
x0: int = ox + (bw - tw) / 2
|
||||
y0: int = oy + (bh - th) / 2
|
||||
y: int = 0
|
||||
while y < th:
|
||||
sy: int = (y * img.height) / th
|
||||
if sy >= img.height: sy = img.height - 1
|
||||
x: int = 0
|
||||
while x < tw:
|
||||
sx: int = (x * img.width) / tw
|
||||
if sx >= img.width: sx = img.width - 1
|
||||
ty: int = y0 + y
|
||||
tx: int = x0 + x
|
||||
if tx >= 0 and ty >= 0 and tx < dw and ty < dh:
|
||||
dst[ty * dw + tx] = img.pixels[sy * img.width + sx]
|
||||
x = x + 1
|
||||
y = y + 1
|
||||
|
||||
def _border(dst: uint32*, dw: int, dh: int, x: int, y: int, w: int, h: int, c: uint32):
|
||||
i: int = 0
|
||||
while i < w:
|
||||
if x + i >= 0 and x + i < dw:
|
||||
if y >= 0 and y < dh: dst[y * dw + x + i] = c
|
||||
if y + h - 1 >= 0 and y + h - 1 < dh: dst[(y + h - 1) * dw + x + i] = c
|
||||
i = i + 1
|
||||
i = 0
|
||||
while i < h:
|
||||
if y + i >= 0 and y + i < dh:
|
||||
if x >= 0 and x < dw: dst[(y + i) * dw + x] = c
|
||||
if x + w - 1 >= 0 and x + w - 1 < dw: dst[(y + i) * dw + x + w - 1] = c
|
||||
i = i + 1
|
||||
|
||||
def _report(path: const str) -> im.Image*:
|
||||
img: im.Image* = imagesee.load(path)
|
||||
if img is None:
|
||||
stdio.printf("[imagesee] %s: FAILED stage=%d\n", path, imagesee.last_error())
|
||||
return None
|
||||
stdio.printf("[imagesee] %s: %s %dx%d sum=%x\n",
|
||||
path, imagesee.format_name(img), img.width, img.height,
|
||||
imagesee.checksum(img))
|
||||
return img
|
||||
|
||||
def _panel_failed(dst: uint32*, w: int, h: int):
|
||||
y: int = 0
|
||||
while y < h:
|
||||
x: int = 0
|
||||
while x < w:
|
||||
c: uint32 = uint32(0xFF300018)
|
||||
if ((x >> 5) + (y >> 5)) & 1:
|
||||
c = uint32(0xFF001830)
|
||||
dst[y * w + x] = c
|
||||
x = x + 1
|
||||
y = y + 1
|
||||
|
||||
@__attribute__(section(".text.startup"), aligned(16))
|
||||
export def _start() -> int:
|
||||
stdio.printf("[imagesee] app start\n")
|
||||
|
||||
png_img: im.Image* = _report("/IMAGES/SAMPLE.PNG")
|
||||
jpg_img: im.Image* = _report("/IMAGES/PHOTO.JPG")
|
||||
_report("/IMAGES/GRAY.JPG")
|
||||
_report("/IMAGES/PAL.PNG")
|
||||
_report("/IMAGES/INTER.PNG")
|
||||
|
||||
root: vpui.Tk = vpui.Tk()
|
||||
root.title("ImageSee - PNG | JPEG")
|
||||
root.set_style(window.STYLE_WIN11)
|
||||
root.geometry(WIN_X, WIN_Y, WIN_W, WIN_H)
|
||||
root.bg_color = uint32(BG)
|
||||
master: vpui.Tk* = &root
|
||||
|
||||
title: vpui.Label = vpui.Label(master, "ImageSee left: PNG right: JPEG", uint32(0xFFE0E0F0))
|
||||
root.pack(0, vpui.PACK_TOP, vpui.FILL_X, 0, 12, 6)
|
||||
|
||||
fb1: uint32* = uint32*(stdlib.malloc(uint64(CANVAS_W * CANVAS_H * 4)))
|
||||
fb2: uint32* = uint32*(stdlib.malloc(uint64(CANVAS_W * CANVAS_H * 4)))
|
||||
if fb1 is None or fb2 is None:
|
||||
process.exit(1)
|
||||
return 0
|
||||
_checker(fb1, CANVAS_W, CANVAS_H)
|
||||
_checker(fb2, CANVAS_W, CANVAS_H)
|
||||
if png_img is None:
|
||||
_panel_failed(fb1, CANVAS_W, CANVAS_H)
|
||||
else:
|
||||
_draw_fit(fb1, CANVAS_W, CANVAS_H, png_img, 8, 8, CANVAS_W - 16, CANVAS_H - 16)
|
||||
if jpg_img is None:
|
||||
_panel_failed(fb2, CANVAS_W, CANVAS_H)
|
||||
else:
|
||||
_draw_fit(fb2, CANVAS_W, CANVAS_H, jpg_img, 8, 8, CANVAS_W - 16, CANVAS_H - 16)
|
||||
_border(fb1, CANVAS_W, CANVAS_H, 0, 0, CANVAS_W, CANVAS_H, uint32(BORDER))
|
||||
_border(fb2, CANVAS_W, CANVAS_H, 0, 0, CANVAS_W, CANVAS_H, uint32(BORDER))
|
||||
|
||||
c1: vpui.FBCanvas = vpui.FBCanvas(master, CANVAS_W, CANVAS_H)
|
||||
c1.set_fb(fb1, CANVAS_W, CANVAS_H)
|
||||
root.pack(1, vpui.PACK_LEFT, 0, 1, 10, 8)
|
||||
c2: vpui.FBCanvas = vpui.FBCanvas(master, CANVAS_W, CANVAS_H)
|
||||
c2.set_fb(fb2, CANVAS_W, CANVAS_H)
|
||||
root.pack(2, vpui.PACK_LEFT, 0, 1, 10, 8)
|
||||
|
||||
stdio.printf("[imagesee] window ready\n")
|
||||
root.mainloop()
|
||||
process.exit(0)
|
||||
return 0
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "ImageSee",
|
||||
"version": "1.0.0",
|
||||
"source_dir": "./",
|
||||
"build_dir": "./__vpcbuild__",
|
||||
"compiler": {
|
||||
"cmd": "llc",
|
||||
"flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=static", "-O2"]
|
||||
},
|
||||
"linker": {
|
||||
"cmd": "ld.lld.exe",
|
||||
"flags": [
|
||||
"-m", "elf_x86_64",
|
||||
"-T", "linker.ld",
|
||||
"--oformat", "elf64-x86-64",
|
||||
"--strip-all"
|
||||
],
|
||||
"output": "imagesee.elf"
|
||||
},
|
||||
"includes": ["../..", "../../../../includes"],
|
||||
"target": {
|
||||
"triple": "x86_64-none-elf",
|
||||
"datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
},
|
||||
"options": {
|
||||
"slice_level": 3,
|
||||
"target": "llvm",
|
||||
"strict_mode": true
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -20,9 +20,11 @@ A bare-metal 64-bit operating system written in the [Viper](https://git.gvsds.co
|
||||
- **VKernel** —— 64 位微内核(x86_64-none-elf 目标)
|
||||
- **UEFI 引导器** —— `boot/boot.c`(GNU-EFI 编译为 `disk/EFI/BOOT/bootx64.efi`)
|
||||
- **FAT32 驱动** —— 自研实现,支持挂载、目录、文件操作
|
||||
- **4 个用户应用** —— HelloWorld / Terminal / Gargantua / Scene3D
|
||||
- **7 个用户应用** —— HelloWorld / Terminal / Gargantua / Scene3D / Explorer / FontDemo / ImageSee
|
||||
- **1 个动态库** —— SerialLogger
|
||||
- **ViperPOS SDK** —— 用户态开发库(系统调用、窗口、文件系统、进程)
|
||||
- **ViperPOS SDK** —— 用户态开发库(系统调用、窗口、文件系统、进程、图像解码)
|
||||
- **syscall ABI 计划** —— 与 Linux x86-64 对齐的路线见 [`docs/syscall.md`](docs/syscall.md)
|
||||
- **ImageSee** —— PNG/JPEG 解码库与示例应用见 [`docs/imagesee.md`](docs/imagesee.md)
|
||||
|
||||
### 目录结构
|
||||
|
||||
@@ -185,9 +187,11 @@ Main components:
|
||||
- **VKernel** — 64-bit microkernel (target `x86_64-none-elf`)
|
||||
- **UEFI bootloader** — `boot/boot.c` (compiled by GNU-EFI to `disk/EFI/BOOT/bootx64.efi`)
|
||||
- **FAT32 driver** — self-implemented, supports mount/dir/file operations
|
||||
- **4 user applications** — HelloWorld / Terminal / Gargantua / Scene3D
|
||||
- **7 user applications** — HelloWorld / Terminal / Gargantua / Scene3D / Explorer / FontDemo / ImageSee
|
||||
- **1 dynamic library** — SerialLogger
|
||||
- **ViperPOS SDK** — user-space development kit (syscall, window, fs, process)
|
||||
- **ViperPOS SDK** — user-space development kit (syscall, window, fs, process, image decoding)
|
||||
- **Syscall ABI plan** — Linux x86-64 alignment roadmap in [`docs/syscall.md`](docs/syscall.md)
|
||||
- **ImageSee** — PNG/JPEG decoder library and demo app, see [`docs/imagesee.md`](docs/imagesee.md)
|
||||
|
||||
### Directory layout
|
||||
|
||||
|
||||
@@ -88,3 +88,82 @@ Write-Host "Unmounting disk image..."
|
||||
imdisk -D -m $MOUNT_LETTER
|
||||
|
||||
Write-Host "Disk image created: $DISK_IMG" -ForegroundColor Green
|
||||
|
||||
# ==========================================================
|
||||
# Refresh the VMware disk next to the .vmx
|
||||
# ==========================================================
|
||||
# Vmware/ViperOS.vmdk is a monolithicFlat descriptor; the data lives in
|
||||
# ViperOS-flat.vmdk. Both are regenerated from disk.img so "Open VM" always
|
||||
# sees the image that was just packed. The .vmx already points at
|
||||
# ide0:0.fileName = "ViperOS.vmdk"; it uses EFI firmware for bootx64.efi.
|
||||
$VMWARE_DIR = Join-Path $PROJECT_DIR "Vmware"
|
||||
$VMWARE_VMDK = Join-Path $VMWARE_DIR "ViperOS.vmdk"
|
||||
$VMWARE_FLAT = Join-Path $VMWARE_DIR "ViperOS-flat.vmdk"
|
||||
$VMWARE_VMX = Join-Path $VMWARE_DIR "Viper OS.vmx"
|
||||
|
||||
if (Test-Path $VMWARE_DIR) {
|
||||
Write-Host "=== Updating VMware disk ===" -ForegroundColor Cyan
|
||||
$vmRunning = Get-Process -Name "vmware-vmx" -ErrorAction SilentlyContinue
|
||||
if ($vmRunning) {
|
||||
Write-Host "Warning: a VMware VM is running; close it and re-run to refresh the VMDK." -ForegroundColor Yellow
|
||||
} else {
|
||||
# A leftover .lck from a crash keeps VMware from opening the VM.
|
||||
$vmLock = "$VMWARE_VMX.lck"
|
||||
if (Test-Path $vmLock) {
|
||||
Write-Host "Removing stale VMware lock: $vmLock" -ForegroundColor Yellow
|
||||
Remove-Item -Recurse -Force $vmLock -ErrorAction SilentlyContinue
|
||||
}
|
||||
$size = (Get-Item $DISK_IMG).Length
|
||||
$sectors = [int64]($size / 512)
|
||||
$cyl = [int64][math]::Floor($sectors / (16 * 63))
|
||||
|
||||
# Keep the existing CID/UUID so VMware treats it as the same disk.
|
||||
$cid = "fffffffe"
|
||||
$uuid = "60 00 C2 96 88 c0 09 6d-c8 30 f7 71 57 ce 47 0d"
|
||||
$longid = "6af48e7def7fae6bfa57a4affffffffe"
|
||||
if (Test-Path $VMWARE_VMDK) {
|
||||
foreach ($l in (Get-Content $VMWARE_VMDK -ErrorAction SilentlyContinue)) {
|
||||
if ($l -match '^CID=([0-9a-fA-F]+)') { $cid = $Matches[1] }
|
||||
if ($l -match '^ddb\.uuid\s*=\s*"(.+)"') { $uuid = $Matches[1] }
|
||||
if ($l -match '^ddb\.longContentID\s*=\s*"(.+)"') { $longid = $Matches[1] }
|
||||
}
|
||||
}
|
||||
|
||||
$descriptor = @"
|
||||
# Disk DescriptorFile
|
||||
version=1
|
||||
encoding="GBK"
|
||||
CID=$cid
|
||||
parentCID=ffffffff
|
||||
createType="monolithicFlat"
|
||||
|
||||
# Extent description
|
||||
RW $sectors FLAT "ViperOS-flat.vmdk" 0
|
||||
|
||||
# The Disk Data Base
|
||||
#DDB
|
||||
|
||||
ddb.adapterType = "ide"
|
||||
ddb.geometry.cylinders = "$cyl"
|
||||
ddb.geometry.heads = "16"
|
||||
ddb.geometry.sectors = "63"
|
||||
ddb.longContentID = "$longid"
|
||||
ddb.uuid = "$uuid"
|
||||
ddb.virtualHWVersion = "21"
|
||||
"@
|
||||
|
||||
try {
|
||||
if (Test-Path $VMWARE_FLAT) { Remove-Item -Force $VMWARE_FLAT }
|
||||
Copy-Item -Force $DISK_IMG $VMWARE_FLAT
|
||||
Set-Content -Path $VMWARE_VMDK -Value $descriptor -Encoding ASCII
|
||||
if (-not (Select-String -Path $VMWARE_VMX -Pattern 'ide0:0\.fileName\s*=\s*"ViperOS\.vmdk"' -Quiet)) {
|
||||
Write-Host "Note: $VMWARE_VMX does not reference ViperOS.vmdk; check ide0:0.fileName." -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host "VMDK updated: $VMWARE_VMDK ($sectors sectors, $([math]::Round($size / 1MB)) MiB)" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "Warning: failed to refresh the VMware disk: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Host "Note: Vmware directory not found; skipping VMDK update." -ForegroundColor DarkGray
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the sample images shipped with the ImageSee demo.
|
||||
|
||||
Run from the ViperOS root:
|
||||
python Scripts/gen_imagesee_assets.py
|
||||
|
||||
The generated files live in Assets/images/ and are copied to /IMAGES on the
|
||||
ViperOS disk by build.py. Requires Pillow (only for generating the assets;
|
||||
the OS itself does not need it).
|
||||
"""
|
||||
|
||||
import os
|
||||
import struct
|
||||
import zlib
|
||||
from PIL import Image, ImageDraw, ImageFilter
|
||||
|
||||
|
||||
W, H = 320, 220
|
||||
OUT = os.path.join("Assets", "images")
|
||||
BG_OUT = os.path.join("Assets", "background.jpg")
|
||||
|
||||
|
||||
def test_pattern():
|
||||
img = Image.new("RGBA", (W, H), (0, 0, 0, 255))
|
||||
px = img.load()
|
||||
for y in range(H):
|
||||
for x in range(W):
|
||||
r = (x * 255) // (W - 1)
|
||||
g = (y * 255) // (H - 1)
|
||||
b = 255 - ((x + y) * 255) // (W + H - 2)
|
||||
px[x, y] = (r, g, b, 255)
|
||||
d = ImageDraw.Draw(img)
|
||||
d.rectangle([12, 12, 92, 72], fill=(255, 255, 255, 255))
|
||||
d.rectangle([22, 22, 82, 62], fill=(0, 0, 0, 255))
|
||||
d.rectangle([108, 12, 188, 72], fill=(230, 40, 40, 255))
|
||||
d.rectangle([198, 12, 278, 72], fill=(40, 200, 80, 255))
|
||||
d.rectangle([288, 12, 308, 72], fill=(40, 80, 230, 255))
|
||||
d.ellipse([40, 100, 160, 210], fill=(255, 220, 40, 255))
|
||||
d.ellipse([150, 90, 250, 190], outline=(255, 255, 255, 255), width=5)
|
||||
d.line([0, H - 1, W - 1, 80], fill=(255, 0, 255, 255), width=3)
|
||||
d.line([0, 80, W - 1, H - 1], fill=(0, 255, 255, 255), width=3)
|
||||
for i in range(0, 40):
|
||||
d.rectangle([280 - i, 160 + i, 286 - i, 166 + i], fill=(i * 6, i * 6, i * 6, 255))
|
||||
return img
|
||||
|
||||
|
||||
def png_chunk(typ: bytes, data: bytes) -> bytes:
|
||||
c = struct.pack(">I", len(data)) + typ + data
|
||||
c += struct.pack(">I", zlib.crc32(typ + data) & 0xFFFFFFFF)
|
||||
return c
|
||||
|
||||
|
||||
def write_interlaced_png(path, img):
|
||||
"""Adam7 interlaced RGBA PNG (filter 0), to exercise the pass decoder."""
|
||||
w, h = img.size
|
||||
px = img.load()
|
||||
passes = [(0, 0, 8, 8), (4, 0, 8, 8), (0, 4, 4, 8), (2, 0, 4, 4),
|
||||
(0, 2, 2, 4), (1, 0, 2, 2), (0, 1, 1, 2)]
|
||||
raw = bytearray()
|
||||
for x0, y0, dx, dy in passes:
|
||||
pw = (w - x0 + dx - 1) // dx if w > x0 else 0
|
||||
ph = (h - y0 + dy - 1) // dy if h > y0 else 0
|
||||
if pw <= 0 or ph <= 0:
|
||||
continue
|
||||
for ry in range(ph):
|
||||
raw.append(0) # filter: None
|
||||
y = y0 + ry * dy
|
||||
for rx in range(pw):
|
||||
x = x0 + rx * dx
|
||||
r, g, b, a = px[x, y]
|
||||
raw += bytes((r, g, b, a))
|
||||
ihdr = struct.pack(">IIBBBBB", w, h, 8, 6, 0, 0, 1)
|
||||
out = b"\x89PNG\r\n\x1a\n" + png_chunk(b"IHDR", ihdr)
|
||||
out += png_chunk(b"IDAT", zlib.compress(bytes(raw), 9))
|
||||
out += png_chunk(b"IEND", b"")
|
||||
with open(path, "wb") as f:
|
||||
f.write(out)
|
||||
|
||||
|
||||
def make_background():
|
||||
"""960x540 desktop wallpaper shipped as /sys/images/screen/background.jpg."""
|
||||
w, h = 960, 540
|
||||
base = Image.new("RGB", (w, h))
|
||||
px = base.load()
|
||||
for y in range(h):
|
||||
t = y / (h - 1)
|
||||
r = int(12 + 34 * t)
|
||||
g = int(16 + 20 * t)
|
||||
b = int(34 + 44 * t)
|
||||
for x in range(w):
|
||||
# subtle diagonal tint
|
||||
u = (x / (w - 1) + t) * 0.5
|
||||
px[x, y] = (min(255, r + int(10 * u)),
|
||||
min(255, g + int(6 * u)),
|
||||
min(255, b + int(18 * u)))
|
||||
overlay = Image.new("RGBA", (w, h), (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(overlay)
|
||||
blobs = [
|
||||
(140, 420, 210, (30, 120, 190, 120)),
|
||||
(330, 140, 170, (150, 40, 180, 110)),
|
||||
(700, 360, 260, (20, 90, 160, 120)),
|
||||
(820, 110, 150, (220, 60, 130, 90)),
|
||||
(520, 470, 190, (40, 170, 170, 90)),
|
||||
]
|
||||
for cx, cy, rr, col in blobs:
|
||||
d.ellipse([cx - rr, cy - rr, cx + rr, cy + rr], fill=col)
|
||||
overlay = overlay.filter(ImageFilter.GaussianBlur(30))
|
||||
base = Image.alpha_composite(base.convert("RGBA"), overlay).convert("RGB")
|
||||
# vignette keeps the corners dark so white window chrome stays readable
|
||||
mask = Image.new("L", (w, h))
|
||||
mp = mask.load()
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
dx = (x - w / 2) / (w / 2)
|
||||
dy = (y - h / 2) / (h / 2)
|
||||
d2 = dx * dx + dy * dy
|
||||
v = 255 - int(min(150, d2 * 110))
|
||||
mp[x, y] = max(0, v)
|
||||
base = Image.composite(base, Image.new("RGB", (w, h), (0, 0, 0)), mask)
|
||||
base.save(BG_OUT, "JPEG", quality=88, subsampling=2, optimize=False)
|
||||
print("[assets] wrote", BG_OUT)
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
img = test_pattern()
|
||||
|
||||
# Opaque RGBA PNG (exercises color type 6, non-interlaced).
|
||||
img.save(os.path.join(OUT, "sample.png"), "PNG")
|
||||
|
||||
# Palette PNG with one transparent color (color type 3 + tRNS).
|
||||
pal = img.convert("RGB").quantize(colors=64, method=Image.MEDIANCUT)
|
||||
pal.info["transparency"] = 0
|
||||
pal.save(os.path.join(OUT, "pal.png"), "PNG", transparency=0)
|
||||
|
||||
# Baseline 4:2:0 JPEG.
|
||||
img.convert("RGB").save(os.path.join(OUT, "photo.jpg"), "JPEG",
|
||||
quality=90, subsampling=2, optimize=False)
|
||||
|
||||
# Grayscale JPEG (single component path).
|
||||
img.convert("L").save(os.path.join(OUT, "gray.jpg"), "JPEG", quality=90)
|
||||
|
||||
# Interlaced RGBA PNG with a vertical alpha ramp (Adam7 + alpha).
|
||||
inter = img.copy()
|
||||
ip = inter.load()
|
||||
for y in range(H):
|
||||
for x in range(W):
|
||||
r, g, b, a = ip[x, y]
|
||||
ip[x, y] = (r, g, b, (x * 255) // (W - 1))
|
||||
write_interlaced_png(os.path.join(OUT, "inter.png"), inter)
|
||||
|
||||
make_background()
|
||||
|
||||
# Reference samples for the app's serial output.
|
||||
print("[assets] reference samples (PNG/JPEG decode with PIL):")
|
||||
for name in ("sample.png", "photo.jpg", "gray.jpg",
|
||||
"pal.png", "inter.png"):
|
||||
path = os.path.join(OUT, name)
|
||||
ref = Image.open(path).convert("RGBA")
|
||||
pts = [(ref.width // 4, ref.height // 3),
|
||||
(ref.width // 2, ref.height // 2),
|
||||
((ref.width * 3) // 4, (ref.height * 2) // 3)]
|
||||
vals = [ref.getpixel(p) for p in pts]
|
||||
print(f"[assets] {name:12s} {ref.width}x{ref.height} " +
|
||||
" ".join(f"{p[0]},{p[1]},{p[2]}" for p in vals))
|
||||
print("[assets] wrote", OUT)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Canonical ViperOS syscall table.
|
||||
|
||||
The kernel and vpsdk keep separate copies (vpsdk must not depend on VKernel),
|
||||
so both files are generated from this list. Never edit the generated block
|
||||
by hand:
|
||||
|
||||
# BEGIN GENERATED SYSCALL TABLE
|
||||
...
|
||||
# END GENERATED SYSCALL TABLE
|
||||
|
||||
Usage:
|
||||
python Scripts/syscall_table.py --write # regenerate both files
|
||||
python Scripts/syscall_table.py --check # fail if out of sync
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
TARGETS = [
|
||||
ROOT / "VKernel" / "Kernel" / "intr" / "syscall.vp",
|
||||
ROOT / "vpsdk" / "syscall.vp",
|
||||
]
|
||||
|
||||
BEGIN = "# BEGIN GENERATED SYSCALL TABLE"
|
||||
END = "# END GENERATED SYSCALL TABLE"
|
||||
|
||||
# (kind, payload...) kind: "section" | "entry"
|
||||
# Entry numbers follow Linux x86-64; Linux calls we do not implement yet are
|
||||
# simply absent (the dispatcher returns -ENOSYS for unknown numbers).
|
||||
TABLE = [
|
||||
("section", "Linux 对齐段(号 = Linux x86-64 的号)"),
|
||||
("entry", "READ", 0, "read(fd, buf, count)"),
|
||||
("entry", "WRITE", 1, "write(fd, buf, count)"),
|
||||
("entry", "OPEN", 2, "open(path, flags) - Linux O_*"),
|
||||
("entry", "CLOSE", 3, "close(fd)"),
|
||||
("entry", "STAT", 4, "stat(path, fileinfo*) - legacy struct"),
|
||||
("entry", "FSTAT", 5, "fstat(fd, struct stat*)"),
|
||||
("entry", "POLL", 7, "poll(fds, nfds, timeout) - stub 0"),
|
||||
("entry", "SEEK", 8, "lseek(fd, offset, whence) -> new offset"),
|
||||
("entry", "MMAP", 9, "mmap(addr, len, prot, flags, fd, off)"),
|
||||
("entry", "MPROTECT", 10, "mprotect(addr, len, prot)"),
|
||||
("entry", "MUNMAP", 11, "munmap(addr, len)"),
|
||||
("entry", "BRK", 12, "brk(addr) -> new break"),
|
||||
("entry", "RT_SIGACTION", 13, "rt_sigaction() - bookkeeping"),
|
||||
("entry", "RT_SIGPROCMASK", 14, "rt_sigprocmask() - bookkeeping"),
|
||||
("entry", "RT_SIGRETURN", 15, "rt_sigreturn()"),
|
||||
("entry", "IOCTL", 16, "ioctl(fd, request, arg)"),
|
||||
("entry", "PREAD64", 17, "pread64(fd, buf, count, offset)"),
|
||||
("entry", "PWRITE64", 18, "pwrite64(fd, buf, count, offset)"),
|
||||
("entry", "READV", 19, "readv(fd, iov, iovcnt)"),
|
||||
("entry", "WRITEV", 20, "writev(fd, iov, iovcnt)"),
|
||||
("entry", "ACCESS", 21, "access(path, mode)"),
|
||||
("entry", "PIPE", 22, "pipe(int[2])"),
|
||||
("entry", "YIELD", 24, "sched_yield()"),
|
||||
("entry", "MREMAP", 25, "mremap(old, oldsz, newsz, flags, newaddr)"),
|
||||
("entry", "MADVISE", 28, "madvise(addr, len, advice)"),
|
||||
("entry", "DUP", 32, "dup(fd)"),
|
||||
("entry", "DUP2", 33, "dup2(oldfd, newfd)"),
|
||||
("entry", "NANOSLEEP", 35, "nanosleep(req, rem)"),
|
||||
("entry", "GETPID", 39, "getpid()"),
|
||||
("entry", "CLONE", 56, "clone(flags, stack, ptid, ctid, tls)"),
|
||||
("entry", "FORK", 57, "fork() - copy address space"),
|
||||
("entry", "SPAWN", 59, "execve(path, argv, envp)"),
|
||||
("entry", "THREAD_EXIT", 60, "exit(): retire current thread"),
|
||||
("entry", "WAIT4", 61, "wait4(pid, status, options, rusage)"),
|
||||
("entry", "KILL", 62, "kill(pid, sig) - bookkeeping"),
|
||||
("entry", "UNAME", 63, "uname(utsname*)"),
|
||||
("entry", "FCNTL", 72, "fcntl(fd, cmd, arg)"),
|
||||
("entry", "GETCWD", 79, "getcwd(buf, size)"),
|
||||
("entry", "CHDIR", 80, "chdir(path)"),
|
||||
("entry", "RENAME", 82, "rename(old, new)"),
|
||||
("entry", "MKDIR", 83, "mkdir(path)"),
|
||||
("entry", "RMDIR", 84, "rmdir(path)"),
|
||||
("entry", "REMOVE", 87, "unlink(path)"),
|
||||
("entry", "READLINK", 89, "readlink(path, buf, size) - no symlinks"),
|
||||
("entry", "GETUID", 102, "getuid()"),
|
||||
("entry", "GETGID", 104, "getgid()"),
|
||||
("entry", "GETEUID", 107, "geteuid()"),
|
||||
("entry", "GETEGID", 108, "getegid()"),
|
||||
("entry", "GETTIMEOFDAY", 96, "gettimeofday(tv, tz)"),
|
||||
("entry", "GETPPID", 110, "getppid()"),
|
||||
("entry", "ARCH_PRCTL", 158, "arch_prctl(code, addr)"),
|
||||
("entry", "REBOOT_LINUX", 169, "reboot(magic1, magic2, cmd, arg)"),
|
||||
("entry", "GETTID", 186, "gettid()"),
|
||||
("entry", "FUTEX", 202, "futex(uaddr, op, val, timeout)"),
|
||||
("entry", "GETDENTS64", 217, "getdents64(fd, buf, count)"),
|
||||
("entry", "SET_TID_ADDRESS", 218, "set_tid_address(tidptr)"),
|
||||
("entry", "CLOCK_GETTIME", 228, "clock_gettime(clockid, timespec*)"),
|
||||
("entry", "CLOCK_GETRES", 229, "clock_getres(clockid, timespec*)"),
|
||||
("entry", "EXIT", 231, "exit_group(): terminate all threads"),
|
||||
("entry", "OPENAT", 257, "openat(dirfd, path, flags, mode)"),
|
||||
("entry", "NEWFSTATAT", 262, "newfstatat(dirfd, path, stat*, flags)"),
|
||||
("entry", "UNLINKAT", 263, "unlinkat(dirfd, path, flags)"),
|
||||
("entry", "READLINKAT", 267, "readlinkat(dirfd, path, buf, size)"),
|
||||
("entry", "FACCESSAT", 269, "faccessat(dirfd, path, mode, flags)"),
|
||||
("entry", "SET_ROBUST_LIST", 273, "set_robust_list(head, len)"),
|
||||
("entry", "DUP3", 292, "dup3(oldfd, newfd, flags)"),
|
||||
("entry", "PIPE2", 293, "pipe2(int[2], flags)"),
|
||||
("entry", "PRLIMIT64", 302, "prlimit64(pid, resource, new, old)"),
|
||||
("entry", "GETRANDOM", 318, "getrandom(buf, len, flags)"),
|
||||
("entry", "STATX", 332, "statx(...) - -ENOSYS, glibc falls back"),
|
||||
("entry", "RSEQ", 334, "rseq(rseq, len, flags) - -ENOSYS"),
|
||||
("section", "临时私有段(0x1000+):Linux 无对应,逐条迁出"),
|
||||
("entry", "LOAD_SO", 0x1006, "TODO(userspace): dynamic library loading"),
|
||||
("entry", "SO_CALL1", 0x1007, "TODO(userspace): dynamic library call"),
|
||||
("entry", "REBOOT", 0x1008, "TODO(linux): reboot(169)"),
|
||||
("entry", "SHUTDOWN", 0x1009, "TODO(linux): reboot(RB_POWER_OFF)"),
|
||||
("entry", "SPAWN_PROC", 0x100A, "TODO(userspace): Terminal run helper"),
|
||||
("entry", "THREAD_CREATE_OLD", 0x100B, "TODO(userspace): ViperOS thread_create"),
|
||||
]
|
||||
|
||||
|
||||
def render() -> str:
|
||||
out = ["enum Syscall:"]
|
||||
for item in TABLE:
|
||||
if item[0] == "section":
|
||||
out.append(f" # ---- {item[1]} ----")
|
||||
continue
|
||||
_, name, num, comment = item
|
||||
value = f"0x{num:04X}" if num >= 0x1000 else f"{num}"
|
||||
out.append(f" {name:<18}= {value:<7}# {comment}")
|
||||
return "\n".join(out) + "\n"
|
||||
|
||||
|
||||
def split_lines(text: str):
|
||||
"""Split keeping line endings (the tree mixes CRLF and a few LF lines)."""
|
||||
return text.splitlines(keepends=True)
|
||||
|
||||
|
||||
def replace_block(text: str) -> str:
|
||||
lines = split_lines(text)
|
||||
b = e = -1
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.strip()
|
||||
if stripped == BEGIN:
|
||||
b = i
|
||||
elif stripped == END:
|
||||
e = i
|
||||
break
|
||||
if b < 0 or e < 0 or e <= b:
|
||||
raise SystemExit(f"markers not found: {BEGIN} / {END}")
|
||||
newline = "\r\n" if lines[b].endswith("\r\n") else "\n"
|
||||
block = [ln + newline for ln in render().splitlines()]
|
||||
return "".join(lines[: b + 1] + block + lines[e:])
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--check", action="store_true")
|
||||
ap.add_argument("--write", action="store_true")
|
||||
args = ap.parse_args()
|
||||
if not args.check and not args.write:
|
||||
ap.error("pass --check or --write")
|
||||
|
||||
stale = []
|
||||
for path in TARGETS:
|
||||
if not path.exists():
|
||||
print(f"[syscall] missing {path}")
|
||||
return 1
|
||||
with open(path, encoding="utf-8", newline="") as fh:
|
||||
old = fh.read()
|
||||
new = replace_block(old)
|
||||
if new != old:
|
||||
stale.append(path)
|
||||
if args.write:
|
||||
with open(path, "w", encoding="utf-8", newline="") as fh:
|
||||
fh.write(new)
|
||||
print(f"[syscall] updated {path.relative_to(ROOT)}")
|
||||
else:
|
||||
print(f"[syscall] ok {path.relative_to(ROOT)}")
|
||||
|
||||
if args.check and stale:
|
||||
print("[syscall] out of sync; run: python Scripts/syscall_table.py --write")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -111,6 +111,10 @@ virtual class devfs_file:
|
||||
# mmap 支持:默认不可映射(返回 -1)。可映射设备(/dev/fb0)覆写它,
|
||||
# 把自己的物理区间交给内核 mmap 去映射进用户地址空间。
|
||||
def mmap_info(self, fd: int, out_phys: uint64*, out_size: uint64*) -> int: return -1
|
||||
# 1 = 映射该设备的用户页要按 MMIO 处理(PTE_PCD,禁用缓存)。
|
||||
# framebuffer 必须返回 1:VMware/真机上写回缓存的 MMIO 写入可能不会立刻
|
||||
# 反映到扫描输出,表现为系统还在跑但屏幕停在旧画面。共享内存(/dev/desk)返回 0。
|
||||
def mmap_uncached(self) -> int: return 0
|
||||
|
||||
|
||||
class _DevFSObject:
|
||||
@@ -288,6 +292,12 @@ class _DevFSObject:
|
||||
file_ptr: devfs_file* = fd_table[fd].file
|
||||
return file_ptr.mmap_info(fd, out_phys, out_size)
|
||||
|
||||
def get_mmap_uncached(self, fd: int) -> int:
|
||||
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
|
||||
return 0
|
||||
file_ptr: devfs_file* = fd_table[fd].file
|
||||
return file_ptr.mmap_uncached()
|
||||
|
||||
def list_directory(self, path: const str,
|
||||
buffer: str,
|
||||
size: uint32) -> int:
|
||||
@@ -332,6 +342,9 @@ def devfs_ioctl(fd: int, cmd: uint32, arg: rawptr) -> int:
|
||||
def devfs_get_mmap_info(fd: int, out_phys: uint64*, out_size: uint64*) -> int:
|
||||
return DevFSObject.get_mmap_info(fd, out_phys, out_size)
|
||||
|
||||
def devfs_mmap_uncached(fd: int) -> int:
|
||||
return DevFSObject.get_mmap_uncached(fd)
|
||||
|
||||
def devfs_list_directory(path: const str, buffer: str, size: uint32) -> int:
|
||||
return DevFSObject.list_directory(path, buffer, size)
|
||||
|
||||
@@ -527,11 +540,15 @@ virtual class framebuffer_device(devfs_file):
|
||||
if request == FB_WAIT_VBLANK:
|
||||
# 等进入垂直回扫期(0x3DA bit3)。返回等待消耗的迭代数:到上限即超时
|
||||
#(当前没有 VGA 回扫信号),调用方可据此判断同步是否真的生效。
|
||||
t: int = 100000
|
||||
while (asm.inb(0x3DA) & 0x08) != 0 and t > 0: t -= 1
|
||||
t2: int = 100000
|
||||
while (asm.inb(0x3DA) & 0x08) == 0 and t2 > 0: t2 -= 1
|
||||
return 100000 - t2
|
||||
t: int = 2048
|
||||
while (asm.inb(0x3DA) & 0x08) != 0 and t > 0:
|
||||
t -= 1
|
||||
asm.pause()
|
||||
t2: int = 2048
|
||||
while (asm.inb(0x3DA) & 0x08) == 0 and t2 > 0:
|
||||
t2 -= 1
|
||||
asm.pause()
|
||||
return 2048 - t2
|
||||
if request != FB_GET_INFO or not arg:
|
||||
return -1
|
||||
info: FbInfo* = FbInfo*(uint64(arg))
|
||||
@@ -548,6 +565,8 @@ virtual class framebuffer_device(devfs_file):
|
||||
if out_size: *out_size = self.fb_size
|
||||
return 0
|
||||
|
||||
def mmap_uncached(self) -> int: return 1
|
||||
|
||||
fb0_file: framebuffer_device* = framebuffer_device()
|
||||
|
||||
def devfs_register_fb0(fb: rawptr, width: int, height: int, pitch: int, format: int, size: uint64) -> int:
|
||||
|
||||
@@ -31,12 +31,10 @@ def read(pdrv: uint8, buff: uint8*, sector: uint32, count: uint32) -> types.DRES
|
||||
if pdrv >= MAX_DRIVES: return types.DRESULT.RES_PARERR
|
||||
if not drive_initialized[pdrv]: return types.DRESULT.RES_NOTRDY
|
||||
drive_lock.lock()
|
||||
i: uint32
|
||||
for i in range(count):
|
||||
rd_res: int = ide.readSector(sector + i, buff + (i * 512))
|
||||
if rd_res != 0:
|
||||
drive_lock.unlock()
|
||||
return types.DRESULT.RES_ERROR
|
||||
rd_res: int = ide.readSectors(sector, buff, count)
|
||||
if rd_res != 0:
|
||||
drive_lock.unlock()
|
||||
return types.DRESULT.RES_ERROR
|
||||
drive_lock.unlock()
|
||||
return types.DRESULT.RES_OK
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ import fat32_time as ftime
|
||||
import viperlib
|
||||
import drivers.serial.uart.serial as serial
|
||||
|
||||
# Max sectors per coalesced file read (64 KiB); the IDE layer further caps DMA
|
||||
# commands at 128 sectors.
|
||||
define DMA_READ_CLUSTER_CAP = 128
|
||||
|
||||
def _read32(buf: uint8*, off: uint32) -> uint32:
|
||||
return uint32(buf[off]) | (uint32(buf[off + 1]) << 8) | (uint32(buf[off + 2]) << 16) | (uint32(buf[off + 3]) << 24)
|
||||
|
||||
@@ -1525,6 +1529,39 @@ class fileobj:
|
||||
if self.file_pos >= self.file_size: break
|
||||
if self.first_cluster < 2: break
|
||||
if self.current_cluster < 2 or vol.is_eoc(self.current_cluster): break
|
||||
# Fast path: at a cluster boundary, coalesce contiguous clusters and
|
||||
# issue one multi-sector read (BMIDE DMA) instead of one PIO command
|
||||
# per 512-byte sector. Capped at 64 KiB per call; the loop continues.
|
||||
if (self.file_pos % vol.cluster_size) == 0 and remaining >= vol.cluster_size:
|
||||
max_bytes: uint32 = remaining
|
||||
fleft: uint32 = self.file_size - self.file_pos
|
||||
if max_bytes > fleft: max_bytes = fleft
|
||||
max_clusters: uint32 = max_bytes / vol.cluster_size
|
||||
cap_clusters: uint32 = (uint32(DMA_READ_CLUSTER_CAP) * uint32(512)) / vol.cluster_size
|
||||
if max_clusters > cap_clusters: max_clusters = cap_clusters
|
||||
run: uint32 = 0
|
||||
c: uint32 = self.current_cluster
|
||||
next_c: uint32 = 0
|
||||
while run < max_clusters:
|
||||
if c < 2 or vol.is_eoc(c): break
|
||||
run = run + 1
|
||||
next_c = vol.cluster_get_next(c)
|
||||
if next_c != c + 1: break
|
||||
c = next_c
|
||||
if run >= 2:
|
||||
sectors: uint32 = run * uint32(vol.cluster_sectors)
|
||||
abs_sect: uint64 = vol.cluster_to_sector(self.current_cluster) + uint64(vol.hidden_sectors)
|
||||
vol.lock.lock()
|
||||
res_run: DRESULT = diskio.read(vol.pdrv, buf_ptr, uint32(abs_sect), sectors)
|
||||
vol.lock.unlock()
|
||||
if res_run != DRESULT.RES_OK: return FRESULT.FR_DISK_ERR
|
||||
nbytes: uint32 = sectors * uint32(512)
|
||||
buf_ptr = buf_ptr + nbytes
|
||||
self.file_pos = self.file_pos + nbytes
|
||||
total_read = total_read + nbytes
|
||||
remaining = remaining - nbytes
|
||||
self.current_cluster = next_c
|
||||
continue
|
||||
cluster_offset: uint32 = self.file_pos % vol.cluster_size
|
||||
sector_in_cluster: uint32 = cluster_offset / 512
|
||||
offset_in_sector: uint32 = cluster_offset % 512
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import asm
|
||||
import drivers.usb.pci as pci
|
||||
import drivers.serial.uart.serial as serial
|
||||
import paging.paging as paging
|
||||
import viperlib
|
||||
import string
|
||||
|
||||
|
||||
enum IDError:
|
||||
@@ -21,6 +25,119 @@ define SECTOR_SIZE = 512
|
||||
|
||||
_ide_initialized: int = 0
|
||||
|
||||
# ---- Bus-master IDE (BMIDE) ----
|
||||
# One 64 KiB PRD entry per command; the DMA buffer is 64 KiB aligned in BSS so
|
||||
# a single entry never crosses a 64 KiB boundary.
|
||||
define DMA_MAX_SECTORS = 16 # 16 * 512 = 8 KiB per DMA command
|
||||
define DMA_AREA_SIZE = 131072
|
||||
define DMA_SCRATCH_LBA = 0x7FFF0 # near the end of the 256 MiB image; safe to restore
|
||||
define DMA_AREA_SIZE = 131072
|
||||
_bm_base: uint16 = 0
|
||||
_bm_ok: int = 0
|
||||
_dma_area: [DMA_AREA_SIZE]uint8
|
||||
_dma_buf: uint64 = 0
|
||||
_dma_buf_pa: uint64 = 0
|
||||
_prdt_area: [64]uint64
|
||||
_prdt: uint64 = 0
|
||||
_prdt_pa: uint64 = 0
|
||||
_dma_logged: int = 0
|
||||
_dma_tried: int = 0
|
||||
_dma_dir: uint8 = 0x08
|
||||
_pci_cmd: uint16 = 0
|
||||
|
||||
def _mfence():
|
||||
__asm__("mfence", op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
|
||||
def _clflush_range(addr: uint64, nbytes: uint64):
|
||||
a: uint64 = addr & ~uint64(63)
|
||||
end: uint64 = addr + nbytes
|
||||
while a < end:
|
||||
__asm__(f"clflush [{asminp(a, ASM_DESCR.REG_ANY)}]",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
a = a + uint64(64)
|
||||
|
||||
def _ide_reset():
|
||||
asm.outb(0x3F6, 0x04)
|
||||
for _ in range(10000): asm.nop()
|
||||
asm.outb(0x3F6, 0x00)
|
||||
for _ in range(10000): asm.nop()
|
||||
|
||||
# Ask the drive to switch to a DMA transfer mode. After SRST the drive is in
|
||||
# its default PIO mode; READ DMA (0xC8) may be aborted until SET FEATURES runs.
|
||||
# use_mdma=0: Ultra DMA (word 88), use_mdma=1: multiword DMA (word 63).
|
||||
def _set_dma_mode(use_mdma: int) -> int:
|
||||
err: IDError = waitReady(IDE_baseport_PRIMARY)
|
||||
if err != IDError.IDE_ERR_NONE: return -1
|
||||
asm.outb(IDE_baseport_PRIMARY + 6, uint8(0xE0))
|
||||
asm.outb(IDE_baseport_PRIMARY + 7, uint8(0xEC)) # IDENTIFY
|
||||
err = waitDataReady(IDE_baseport_PRIMARY)
|
||||
if err != IDError.IDE_ERR_NONE: return -1
|
||||
ident: [256]uint16
|
||||
i: int = 0
|
||||
while i < 256:
|
||||
ident[i] = asm.inw(IDE_baseport_PRIMARY)
|
||||
i = i + 1
|
||||
mode: int = -1
|
||||
value: int = 0
|
||||
if use_mdma != 0:
|
||||
# word 63 bits 0..7: multiword DMA modes 0..7 (we use 0..2)
|
||||
m: int = 2
|
||||
while m >= 0:
|
||||
if ident[63] & uint16(1 << m):
|
||||
mode = m
|
||||
break
|
||||
m = m - 1
|
||||
if mode < 0: return -1
|
||||
value = 0x20 | mode
|
||||
else:
|
||||
# word 88: Ultra DMA modes supported (bits 0..6). Pick the highest mode
|
||||
# the drive advertises: the firmware/controller timing is usually already
|
||||
# programmed for it.
|
||||
m = 6
|
||||
while m >= 0:
|
||||
if ident[88] & uint16(1 << m):
|
||||
mode = m
|
||||
break
|
||||
m = m - 1
|
||||
if mode < 0: return -1
|
||||
value = 0x40 | mode
|
||||
err = waitReady(IDE_baseport_PRIMARY)
|
||||
if err != IDError.IDE_ERR_NONE: return -1
|
||||
asm.outb(IDE_baseport_PRIMARY + 1, uint8(0x03)) # SET FEATURES: transfer mode
|
||||
asm.outb(IDE_baseport_PRIMARY + 2, uint8(value))
|
||||
asm.outb(IDE_baseport_PRIMARY + 6, uint8(0xE0))
|
||||
asm.outb(IDE_baseport_PRIMARY + 7, uint8(0xEF))
|
||||
err = waitReady(IDE_baseport_PRIMARY)
|
||||
if err != IDError.IDE_ERR_NONE: return -1
|
||||
st: uint8 = asm.inb(IDE_baseport_PRIMARY + 7)
|
||||
if st & uint8(0x01): return -1
|
||||
return mode
|
||||
|
||||
# Read a scratch sector with DMA and compare against the PIO copy. A wrong
|
||||
# bus-master direction bit may write the scratch sector; restore it via PIO.
|
||||
def _dma_selftest(dr: uint8) -> int:
|
||||
global _dma_dir
|
||||
_dma_dir = dr
|
||||
ref: [512]uint8
|
||||
rr: int = readSector(uint32(DMA_SCRATCH_LBA), &ref[0])
|
||||
if rr != IDError.IDE_ERR_NONE: return -1
|
||||
r: int = _dma_read(uint32(DMA_SCRATCH_LBA), 1)
|
||||
ok: int = 0
|
||||
if r == IDError.IDE_ERR_NONE:
|
||||
if string.memcmp(rawptr(_dma_buf), rawptr(&ref[0]), 512) == 0: ok = 1
|
||||
if ok == 0:
|
||||
writeSector(uint32(DMA_SCRATCH_LBA), &ref[0])
|
||||
return -1
|
||||
return 0
|
||||
|
||||
# Reset, set the requested DMA class, and run the direction selftest.
|
||||
def _try_dma(use_mdma: int, dr: uint8) -> int:
|
||||
_ide_reset()
|
||||
m: int = _set_dma_mode(use_mdma)
|
||||
if m < 0: return -1
|
||||
if _dma_selftest(dr) == 0: return m
|
||||
return -1
|
||||
|
||||
def waitReady(baseport: uint16) -> static IDError:
|
||||
timeout: int = 100000
|
||||
for _ in range(timeout, 0, -1):
|
||||
@@ -64,7 +181,8 @@ def flushStaleData():
|
||||
p = p + 1
|
||||
|
||||
def init() -> int:
|
||||
global _ide_initialized
|
||||
global _ide_initialized, _bm_base, _bm_ok, _dma_buf, _prdt, _pci_cmd
|
||||
global _dma_buf_pa, _prdt_pa
|
||||
if _ide_initialized: return IDError.IDE_ERR_NONE
|
||||
bus: uint8
|
||||
dev_n: uint8
|
||||
@@ -75,14 +193,56 @@ def init() -> int:
|
||||
vendor: uint16 = pci.pci_read16(bus, dev_n, func, 0x00)
|
||||
if vendor == 0xFFFF: continue
|
||||
cls: uint32 = pci.pci_get_class(bus, dev_n, func)
|
||||
if cls == 0x010180 or cls == 0x0101:
|
||||
# class/subclass = 0x0101 (IDE); progIF bit7 = bus-master capable.
|
||||
# VMware's PIIX4 reports progIF 0x8A (not exactly 0x80), so do
|
||||
# not compare the whole 24-bit class/progIF word.
|
||||
cls_sub: uint32 = cls & uint32(0xFFFF00)
|
||||
proto: uint32 = cls & uint32(0xFF)
|
||||
if cls_sub == uint32(0x010100) and (proto & uint32(0x80)) != 0:
|
||||
bar4: uint32 = pci.pci_read32(bus, dev_n, func, 0x20)
|
||||
bar0: uint32 = pci.pci_read32(bus, dev_n, func, 0x10)
|
||||
lb0: [128]char
|
||||
viperlib.snprintf(&lb0, 128, "[ide] pci %d:%d.%d cls=0x%x bar0=0x%x bar4=0x%x\n",
|
||||
uint32(bus), uint32(dev_n), uint32(func),
|
||||
cls, bar0, bar4)
|
||||
serial.puts(&lb0)
|
||||
cmd: uint16 = pci.pci_read16(bus, dev_n, func, 0x04)
|
||||
pci.pci_write32(bus, dev_n, func, 0x04, uint32(cmd | 0x0007))
|
||||
if _bm_base == 0 and (bar4 & uint32(1)) != 0:
|
||||
_bm_base = uint16(bar4 & uint32(0xFFFC))
|
||||
_pci_cmd = pci.pci_read16(bus, dev_n, func, 0x04)
|
||||
asm.outb(0x3F6, 0x04)
|
||||
for _ in range(10000): asm.nop()
|
||||
asm.outb(0x3F6, 0x00)
|
||||
for _ in range(10000): asm.nop()
|
||||
status: uint8 = asm.inb(0x1F7)
|
||||
if _bm_base != 0:
|
||||
_dma_buf = (uint64(&_dma_area[0]) + uint64(0xFFFF)) & ~uint64(0xFFFF)
|
||||
_prdt = (uint64(&_prdt_area[0]) + uint64(15)) & ~uint64(15)
|
||||
_dma_buf_pa = paging.GetPagePhys(_dma_buf)
|
||||
if _dma_buf_pa != 0: _dma_buf_pa = _dma_buf_pa + (_dma_buf & uint64(0xFFF))
|
||||
_prdt_pa = paging.GetPagePhys(_prdt)
|
||||
if _prdt_pa != 0: _prdt_pa = _prdt_pa + (_prdt & uint64(0xFFF))
|
||||
lb: [160]char
|
||||
viperlib.snprintf(&lb, 160, "[ide] BMIDE base=0x%x pci_cmd=0x%x dma_va=0x%x dma_pa=0x%x prdt_va=0x%x prdt_pa=0x%x\n",
|
||||
uint32(_bm_base), uint32(_pci_cmd), uint32(_dma_buf), uint32(_dma_buf_pa),
|
||||
uint32(_prdt), uint32(_prdt_pa))
|
||||
serial.puts(&lb)
|
||||
_bm_ok = 1
|
||||
dm: int = _try_dma(0, uint8(0x08))
|
||||
if dm < 0: dm = _try_dma(0, uint8(0x00))
|
||||
if dm < 0: dm = _try_dma(1, uint8(0x08))
|
||||
if dm < 0: dm = _try_dma(1, uint8(0x00))
|
||||
if dm >= 0:
|
||||
mb: [96]char
|
||||
viperlib.snprintf(&mb, 96, "[ide] DMA selftest ok mode=%d dir=0x%x\n",
|
||||
dm, uint32(_dma_dir))
|
||||
serial.puts(&mb)
|
||||
else:
|
||||
_bm_ok = 0
|
||||
serial.puts("[ide] DMA unavailable, using PIO\n")
|
||||
else:
|
||||
serial.puts("[ide] BMIDE unavailable, using PIO\n")
|
||||
_ide_initialized = 1
|
||||
return IDError.IDE_ERR_NONE
|
||||
|
||||
@@ -130,6 +290,116 @@ def readSector(lba: uint32, buffer: rawptr) -> int:
|
||||
# serial.puts(dbg)
|
||||
return IDError.IDE_ERR_NONE
|
||||
|
||||
# One bus-master DMA command (<= 64 KiB) into the internal aligned buffer.
|
||||
def _dma_read(lba: uint32, sectors: uint32) -> int:
|
||||
global _bm_ok, _dma_logged, _dma_tried, _dma_dir
|
||||
if _bm_ok == 0 or _dma_buf == 0 or _prdt == 0:
|
||||
return IDError.IDE_ERR_DEVICE_NOT_READY
|
||||
if sectors == 0 or sectors > uint32(DMA_MAX_SECTORS):
|
||||
return IDError.IDE_ERR_SECTOR_COUNT
|
||||
err: IDError = waitReady(IDE_baseport_PRIMARY)
|
||||
if err != IDError.IDE_ERR_NONE:
|
||||
return err
|
||||
# If a previous PIO command left data in the drive buffer (DRQ=1), command
|
||||
# block writes are ignored. Drain it before starting the DMA command.
|
||||
flushStaleData()
|
||||
# Stop the engine, set direction = device -> memory.
|
||||
asm.outb(_bm_base, uint8(0x00))
|
||||
bytes: uint32 = sectors * uint32(512)
|
||||
buf_pa: uint64 = _dma_buf_pa
|
||||
if buf_pa == 0: buf_pa = _dma_buf
|
||||
prdt_pa: uint64 = _prdt_pa
|
||||
if prdt_pa == 0: prdt_pa = _prdt
|
||||
prd: uint32* = uint32*(_prdt)
|
||||
prd[0] = uint32(buf_pa)
|
||||
# count == 0 means 64 KiB; bit15 is EOT.
|
||||
prd[1] = (bytes & uint32(0xFFFF)) | uint32(0x8000)
|
||||
if _dma_tried == 0:
|
||||
_dma_tried = 1
|
||||
dbg: [160]char
|
||||
viperlib.snprintf(&dbg, 160, "[ide] dma try lba=0x%x n=%u buf=0x%x prdt=0x%x prd=%x,%x\n",
|
||||
lba, sectors, uint32(buf_pa), uint32(prdt_pa), prd[0], prd[1])
|
||||
serial.puts(&dbg)
|
||||
_clflush_range(_prdt, uint64(8))
|
||||
_clflush_range(_dma_buf, uint64(bytes))
|
||||
_mfence()
|
||||
asm.outl(_bm_base + uint16(4), uint32(prdt_pa))
|
||||
asm.outb(_bm_base + uint16(2), uint8(0x06)) # clear error/IRQ
|
||||
# Start the bus-master engine first; the PIIX4 waits for the drive's DMA
|
||||
# request. QEMU tolerates either order.
|
||||
asm.outb(_bm_base, uint8(_dma_dir | uint8(0x01))) # start + direction
|
||||
# ATA READ DMA (LBA28, 0xC8)
|
||||
asm.outb(IDE_baseport_PRIMARY + 2, uint8(sectors & uint32(0xFF)))
|
||||
asm.outb(IDE_baseport_PRIMARY + 3, uint8(lba & uint32(0xFF)))
|
||||
asm.outb(IDE_baseport_PRIMARY + 4, uint8((lba >> 8) & uint32(0xFF)))
|
||||
asm.outb(IDE_baseport_PRIMARY + 5, uint8((lba >> 16) & uint32(0xFF)))
|
||||
asm.outb(IDE_baseport_PRIMARY + 6, uint8(uint32(0xE0) | ((lba >> 24) & uint32(0x0F))))
|
||||
asm.outb(IDE_baseport_PRIMARY + 7, uint8(0xC8))
|
||||
ok: int = 0
|
||||
t: int = 2000000
|
||||
st: uint8 = 0
|
||||
while t > 0:
|
||||
st = asm.inb(_bm_base + uint16(2))
|
||||
if st & uint8(0x02): break # error
|
||||
if st & uint8(0x04): ok = 1; break # interrupt/completion
|
||||
t = t - 1
|
||||
asm.pause()
|
||||
asm.outb(_bm_base, uint8(0x00)) # stop
|
||||
asm.outb(_bm_base + uint16(2), uint8(0x06))
|
||||
ata: uint8 = asm.inb(IDE_baseport_PRIMARY + 7)
|
||||
if ok == 0:
|
||||
fb: [96]char
|
||||
viperlib.snprintf(&fb, 96, "[ide] DMA timeout st=0x%x ata=0x%x\n", uint32(st), uint32(ata))
|
||||
serial.puts(&fb)
|
||||
return IDError.IDE_ERR_TIMEOUT
|
||||
if ata & uint8(0x01):
|
||||
fb2: [96]char
|
||||
viperlib.snprintf(&fb2, 96, "[ide] DMA ata error st=0x%x ata=0x%x\n", uint32(st), uint32(ata))
|
||||
serial.puts(&fb2)
|
||||
return IDError.IDE_ERR_DEVICE_ERROR
|
||||
_clflush_range(_dma_buf, uint64(bytes))
|
||||
_mfence()
|
||||
if _dma_logged == 0:
|
||||
_dma_logged = 1
|
||||
serial.puts("[ide] DMA read ok\n")
|
||||
return IDError.IDE_ERR_NONE
|
||||
|
||||
# Multi-sector read: DMA for >=2 sectors, PIO for a single sector or after a
|
||||
# DMA failure. Caller must hold the drive lock.
|
||||
def readSectors(lba: uint32, buffer: rawptr, count: uint32) -> int:
|
||||
global _bm_ok
|
||||
if buffer is None: return IDError.IDE_ERR_BUFFER_NULL
|
||||
if count == 0: return IDError.IDE_ERR_SECTOR_COUNT
|
||||
if _bm_ok == 0 or count < 2:
|
||||
i: uint32 = 0
|
||||
while i < count:
|
||||
r: int = readSector(lba + i, rawptr(uint64(buffer) + uint64(i) * uint64(512)))
|
||||
if r != IDError.IDE_ERR_NONE: return r
|
||||
i = i + 1
|
||||
return IDError.IDE_ERR_NONE
|
||||
remaining: uint32 = count
|
||||
cur: uint32 = lba
|
||||
dst: uint64 = uint64(buffer)
|
||||
while remaining > 0:
|
||||
chunk: uint32 = remaining
|
||||
if chunk > uint32(DMA_MAX_SECTORS): chunk = uint32(DMA_MAX_SECTORS)
|
||||
r = _dma_read(cur, chunk)
|
||||
if r != IDError.IDE_ERR_NONE:
|
||||
serial.puts("[ide] DMA failed, falling back to PIO\n")
|
||||
_bm_ok = 0
|
||||
_ide_reset()
|
||||
i = 0
|
||||
while i < remaining:
|
||||
rr: int = readSector(cur + i, rawptr(dst + uint64(i) * uint64(512)))
|
||||
if rr != IDError.IDE_ERR_NONE: return rr
|
||||
i = i + 1
|
||||
return IDError.IDE_ERR_NONE
|
||||
string.memcpy(rawptr(dst), rawptr(_dma_buf), uint64(chunk) * uint64(512))
|
||||
dst = dst + uint64(chunk) * uint64(512)
|
||||
cur = cur + chunk
|
||||
remaining = remaining - chunk
|
||||
return IDError.IDE_ERR_NONE
|
||||
|
||||
def writeSector(lba: uint32, buffer: const rawptr) -> int:
|
||||
if buffer is None: return IDError.IDE_ERR_BUFFER_NULL
|
||||
err: IDError = waitReady(IDE_baseport_PRIMARY)
|
||||
|
||||
@@ -32,6 +32,7 @@ define ET_DYN = 3
|
||||
define EM_X86_64 = 62
|
||||
define PT_LOAD = 1
|
||||
define PT_DYNAMIC = 2
|
||||
define PT_INTERP = 3
|
||||
define PF_X = 1
|
||||
define PF_W = 2
|
||||
define PF_R = 4
|
||||
@@ -120,7 +121,7 @@ define MAX_SHDRS = 64
|
||||
define MAX_DYNS = 64
|
||||
define MAX_RELAS = 256
|
||||
|
||||
def load_elf(path: const str, do_map: int = 1) -> rawptr:
|
||||
def load_elf(path: const str, do_map: int = 1, base: uint64 = 0) -> rawptr:
|
||||
fp: fat32_types.fileobj* = fat32.open(path, fat32_types.FA_READ)
|
||||
if fp is None:
|
||||
err: uint32 = fat32.last_error()
|
||||
@@ -180,6 +181,10 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr:
|
||||
serial.puts("[elf] not ET_EXEC or ET_DYN\n")
|
||||
fat32.close(fp)
|
||||
return None
|
||||
if ehdr.e_type == ET_EXEC and base != 0:
|
||||
serial.puts("[elf] ET_EXEC cannot be relocated\n")
|
||||
fat32.close(fp)
|
||||
return None
|
||||
|
||||
if ehdr.e_machine != EM_X86_64:
|
||||
serial.puts("[elf] not x86_64\n")
|
||||
@@ -246,7 +251,12 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr:
|
||||
viperlib.snprintf(&cr2, 80, "[elf] vaddr 0x%lx-0x%lx pages=%lu\n", lowest_vaddr, highest_vaddr_end, total_pages)
|
||||
serial.puts(cr2)
|
||||
|
||||
raw_alloc: rawptr = mm.malloc(total_pages * 0x1000 + 0x1000)
|
||||
# Keep the image offset of the first LOAD segment: the final page table maps
|
||||
# whole pages, so the physical buffer must start at a page boundary that
|
||||
# corresponds to `virt_page_start` below.
|
||||
low_off: uint64 = lowest_vaddr & uint64(0xFFF)
|
||||
alloc_sz: uint64 = total_pages * uint64(0x1000) + uint64(0x2000)
|
||||
raw_alloc: rawptr = mm.malloc(alloc_sz)
|
||||
_db_ra: [80]char
|
||||
viperlib.snprintf(&_db_ra, 80, "[elf] malloc ret raw_alloc=0x%lx\n", uint64(raw_alloc))
|
||||
serial.puts(_db_ra)
|
||||
@@ -255,7 +265,8 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr:
|
||||
fat32.close(fp)
|
||||
return None
|
||||
|
||||
phys_base_aligned: uint64 = (uint64(raw_alloc) + uint64(0xFFF)) & ~uint64(0xFFF)
|
||||
phys_page_start_u: uint64 = (uint64(raw_alloc) + uint64(0xFFF)) & ~uint64(0xFFF)
|
||||
phys_base_aligned: uint64 = phys_page_start_u + low_off
|
||||
phys_base: rawptr = rawptr(phys_base_aligned)
|
||||
|
||||
string.memset(phys_base, 0, total_pages * 0x1000)
|
||||
@@ -280,9 +291,9 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr:
|
||||
|
||||
fat32.close(fp)
|
||||
|
||||
virt_page_start: uint64 = lowest_vaddr & ~uint64(0xFFF)
|
||||
phys_page_start: uint64 = uint64(phys_base) & ~uint64(0xFFF)
|
||||
map_end: uint64 = (highest_vaddr_end + 0xFFF) & ~uint64(0xFFF)
|
||||
virt_page_start: uint64 = (base + lowest_vaddr) & ~uint64(0xFFF)
|
||||
phys_page_start: uint64 = phys_page_start_u
|
||||
map_end: uint64 = (base + highest_vaddr_end + uint64(0xFFF)) & ~uint64(0xFFF)
|
||||
num_map_pages: uint64 = (map_end - virt_page_start) // 0x1000
|
||||
|
||||
# Store mapping info for spawn_elf (used when do_map=0)
|
||||
@@ -298,7 +309,7 @@ def load_elf(path: const str, do_map: int = 1) -> rawptr:
|
||||
page_flags: uint64 = paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER
|
||||
paging.MapPage(virt_addr, phys_addr, page_flags)
|
||||
|
||||
entry_addr: rawptr = rawptr(entry_point)
|
||||
entry_addr: rawptr = rawptr(base + entry_point)
|
||||
|
||||
return entry_addr
|
||||
|
||||
@@ -319,7 +330,7 @@ def run_elf(path: const str) -> int:
|
||||
serial.puts("[elf] returned from entry\n")
|
||||
return 0
|
||||
|
||||
define MAX_SPAWNED = 8
|
||||
define MAX_SPAWNED = 32
|
||||
_elf_entries: [MAX_SPAWNED]rawptr
|
||||
|
||||
# User stack virtual address: top of 48-bit user space, 64KB below
|
||||
@@ -336,6 +347,153 @@ _last_elf_phys_page_start: uint64 = 0
|
||||
_last_elf_virt_page_start: uint64 = 0
|
||||
_last_elf_num_map_pages: uint64 = 0
|
||||
|
||||
# ============================================================================
|
||||
# Linux process startup stack: argc/argv/envp/auxv
|
||||
# ============================================================================
|
||||
|
||||
define AT_NULL = 0
|
||||
define AT_PHDR = 3
|
||||
define AT_PHENT = 4
|
||||
define AT_PHNUM = 5
|
||||
define AT_PAGESZ = 6
|
||||
define AT_BASE = 7
|
||||
define AT_FLAGS = 8
|
||||
define AT_ENTRY = 9
|
||||
define AT_UID = 11
|
||||
define AT_EUID = 12
|
||||
define AT_GID = 13
|
||||
define AT_EGID = 14
|
||||
define AT_PLATFORM = 15
|
||||
define AT_HWCAP = 16
|
||||
define AT_CLKTCK = 17
|
||||
define AT_SECURE = 23
|
||||
define AT_RANDOM = 25
|
||||
define AT_EXECFN = 31
|
||||
|
||||
_rng_stack_state: uint64 = 0x243F6A8885A308D3
|
||||
|
||||
def _stack_rand() -> uint8:
|
||||
global _rng_stack_state
|
||||
_rng_stack_state = _rng_stack_state * uint64(6364136223846793005) + uint64(1442695040888963407)
|
||||
return uint8((_rng_stack_state >> 33) & uint64(0xFF))
|
||||
|
||||
def _stk_u64(buf: uint8*, u: uint64*, v: uint64):
|
||||
if u[0] < uint64(8): return
|
||||
u[0] = u[0] - uint64(8)
|
||||
q: uint64* = uint64*(uint64(buf) + u[0])
|
||||
q[0] = v
|
||||
|
||||
def _stk_str(buf: uint8*, u: uint64*, s: const str, base_va: uint64) -> uint64:
|
||||
n: uint64 = uint64(int(string.strlen(s)) + 1)
|
||||
if n > uint64(256): n = uint64(256)
|
||||
if u[0] < n: return uint64(0)
|
||||
u[0] = u[0] - n
|
||||
dst: uint8* = uint8*(uint64(buf) + u[0])
|
||||
i: int = 0
|
||||
while uint64(i) < n:
|
||||
dst[i] = uint8(s[i])
|
||||
i += 1
|
||||
dst[n - uint64(1)] = uint8(0)
|
||||
return base_va + u[0]
|
||||
|
||||
# Build the stack image in a 4KB scratch buffer, then copy it to the top of the
|
||||
# process' 64KB user stack. Pointers are virtual addresses (USER_STACK_VTOP-based).
|
||||
def _build_argv_stack(p: proc.Process*, path: const str, argv: rawptr, envp: rawptr,
|
||||
phdr_va: uint64, entry_va: uint64, phent: uint64, phnum: uint64,
|
||||
interp_base: uint64) -> int:
|
||||
buf: [4096]uint8
|
||||
string.memset(&buf[0], 0, 4096)
|
||||
base_va: uint64 = USER_STACK_VTOP - uint64(4096)
|
||||
u: uint64 = uint64(4096)
|
||||
path_va: uint64 = _stk_str(&buf[0], &u, path, base_va)
|
||||
argv_va: [17]uint64
|
||||
env_va: [17]uint64
|
||||
argc: int = 0
|
||||
envc: int = 0
|
||||
if argv is not None:
|
||||
av: str* = str*(argv)
|
||||
while argc < 16:
|
||||
sp: str = av[argc]
|
||||
if sp is None: break
|
||||
argv_va[argc] = _stk_str(&buf[0], &u, sp, base_va)
|
||||
argc += 1
|
||||
if argc == 0:
|
||||
argv_va[0] = path_va
|
||||
argc = 1
|
||||
if envp is not None:
|
||||
ev: str* = str*(envp)
|
||||
while envc < 16:
|
||||
sp2: str = ev[envc]
|
||||
if sp2 is None: break
|
||||
env_va[envc] = _stk_str(&buf[0], &u, sp2, base_va)
|
||||
envc += 1
|
||||
plat_va: uint64 = _stk_str(&buf[0], &u, "x86_64", base_va)
|
||||
u = u - uint64(16)
|
||||
ri: int = 0
|
||||
while ri < 16:
|
||||
rp: uint8* = uint8*(uint64(&buf[0]) + u + uint64(ri))
|
||||
rp[0] = _stack_rand()
|
||||
ri += 1
|
||||
random_va: uint64 = base_va + u
|
||||
# keep every pushed word 8-byte aligned; the gap below the strings is unused
|
||||
u = u & ~uint64(7)
|
||||
aux_t: [24]uint64
|
||||
aux_v: [24]uint64
|
||||
naux: int = 0
|
||||
aux_t[naux] = uint64(AT_PHDR); aux_v[naux] = phdr_va; naux += 1
|
||||
aux_t[naux] = uint64(AT_PHENT); aux_v[naux] = phent; naux += 1
|
||||
aux_t[naux] = uint64(AT_PHNUM); aux_v[naux] = phnum; naux += 1
|
||||
aux_t[naux] = uint64(AT_PAGESZ); aux_v[naux] = uint64(4096); naux += 1
|
||||
aux_t[naux] = uint64(AT_BASE); aux_v[naux] = interp_base; naux += 1
|
||||
aux_t[naux] = uint64(AT_FLAGS); aux_v[naux] = uint64(0); naux += 1
|
||||
aux_t[naux] = uint64(AT_ENTRY); aux_v[naux] = entry_va; naux += 1
|
||||
aux_t[naux] = uint64(AT_UID); aux_v[naux] = uint64(0); naux += 1
|
||||
aux_t[naux] = uint64(AT_EUID); aux_v[naux] = uint64(0); naux += 1
|
||||
aux_t[naux] = uint64(AT_GID); aux_v[naux] = uint64(0); naux += 1
|
||||
aux_t[naux] = uint64(AT_EGID); aux_v[naux] = uint64(0); naux += 1
|
||||
aux_t[naux] = uint64(AT_PLATFORM); aux_v[naux] = plat_va; naux += 1
|
||||
aux_t[naux] = uint64(AT_HWCAP); aux_v[naux] = uint64(0); naux += 1
|
||||
aux_t[naux] = uint64(AT_CLKTCK); aux_v[naux] = uint64(100); naux += 1
|
||||
aux_t[naux] = uint64(AT_SECURE); aux_v[naux] = uint64(0); naux += 1
|
||||
aux_t[naux] = uint64(AT_RANDOM); aux_v[naux] = random_va; naux += 1
|
||||
aux_t[naux] = uint64(AT_EXECFN); aux_v[naux] = path_va; naux += 1
|
||||
# memory (low -> high): argc, argv[], NULL, envp[], NULL, auxv[], AT_NULL
|
||||
# stack grows down, so push AT_NULL first and the rest in reverse.
|
||||
_stk_u64(&buf[0], &u, uint64(0))
|
||||
_stk_u64(&buf[0], &u, uint64(0))
|
||||
ai: int = naux - 1
|
||||
while ai >= 0:
|
||||
_stk_u64(&buf[0], &u, aux_v[ai])
|
||||
_stk_u64(&buf[0], &u, aux_t[ai])
|
||||
ai -= 1
|
||||
_stk_u64(&buf[0], &u, uint64(0))
|
||||
ai = envc - 1
|
||||
while ai >= 0:
|
||||
_stk_u64(&buf[0], &u, env_va[ai])
|
||||
ai -= 1
|
||||
_stk_u64(&buf[0], &u, uint64(0))
|
||||
ai = argc - 1
|
||||
while ai >= 0:
|
||||
_stk_u64(&buf[0], &u, argv_va[ai])
|
||||
ai -= 1
|
||||
_stk_u64(&buf[0], &u, uint64(argc))
|
||||
used: uint64 = uint64(4096) - u
|
||||
if used == 0 or used > uint64(4096): return -1
|
||||
dst: uint8* = uint8*(p.user_stack - used)
|
||||
src: uint8* = uint8*(uint64(&buf[0]) + u)
|
||||
string.memcpy(dst, src, used)
|
||||
p.entry_rsp = USER_STACK_VTOP - used
|
||||
return 0
|
||||
|
||||
# Map the most recent load_elf() result into the *active* (cloned) PML4.
|
||||
def _map_last_elf():
|
||||
mi: uint64 = 0
|
||||
while mi < _last_elf_num_map_pages:
|
||||
paging.MapPage(_last_elf_virt_page_start + mi * uint64(0x1000),
|
||||
_last_elf_phys_page_start + mi * uint64(0x1000),
|
||||
paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER)
|
||||
mi += 1
|
||||
|
||||
def _elf_entry_wrapper(arg: rawptr) -> int:
|
||||
pid: int = int(uint64(arg))
|
||||
_db1: [64]char
|
||||
@@ -376,13 +534,24 @@ def _elf_entry_wrapper(arg: rawptr) -> int:
|
||||
# will set RFLAGS.IF from the saved frame (0x202) for user mode.
|
||||
# This prevents timer interrupt preemption between TSS setup and iretq.
|
||||
# Drop to Ring 3 user mode via iretq
|
||||
proc.drop_to_user_mode(entry_fn, user_stack_vtop, cur_p.kern_stack)
|
||||
user_rsp: uint64 = USER_STACK_VTOP
|
||||
if cur_p.entry_rsp != 0:
|
||||
user_rsp = cur_p.entry_rsp
|
||||
proc.drop_to_user_mode(entry_fn, user_rsp, cur_p.kern_stack)
|
||||
cur: proc.Process* = proc.ProcessManager.current()
|
||||
cur.exit()
|
||||
return 0
|
||||
|
||||
def spawn_elf(path: const str) -> int:
|
||||
def spawn_elf(path: const str, argv: rawptr = None, envp: rawptr = None) -> int:
|
||||
if ELF_LOAD == 0: return -1 # [临时] 见文件头 ELF_LOAD 开关说明
|
||||
phdr_va: uint64 = 0
|
||||
phent_u: uint64 = 0
|
||||
phnum_u: uint64 = 0
|
||||
main_base: uint64 = 0
|
||||
main_entry: uint64 = 0
|
||||
interp_len: int = 0
|
||||
interp_path: [256]char
|
||||
string.memset(&interp_path[0], 0, 256)
|
||||
fat32.lock()
|
||||
name_start: int = 0
|
||||
si: int = 0
|
||||
@@ -408,17 +577,35 @@ def spawn_elf(path: const str) -> int:
|
||||
fat32.read(fp_scan, &ehdr_buf_s, 64)
|
||||
ehdr_s: Elf64_Ehdr* = &ehdr_buf_s
|
||||
if ehdr_s.e_type == ET_EXEC or ehdr_s.e_type == ET_DYN:
|
||||
if ehdr_s.e_type == ET_DYN:
|
||||
main_base = uint64(0x60000000)
|
||||
main_entry = main_base + ehdr_s.e_entry
|
||||
phoff_s: uint64 = ehdr_s.e_phoff
|
||||
phentsize_s: uint16 = ehdr_s.e_phentsize
|
||||
phnum_s: uint16 = ehdr_s.e_phnum
|
||||
if phentsize_s > 0 and phnum_s > 0 and phnum_s <= MAX_PHDRS:
|
||||
phdr_bufs_s: [MAX_PHDRS]Elf64_Phdr
|
||||
string.memset(&phdr_bufs_s, 0, MAX_PHDRS * 56)
|
||||
phent_u = uint64(phentsize_s)
|
||||
phnum_u = uint64(phnum_s)
|
||||
found_load_s: int = 0
|
||||
for pi_s in range(phnum_s):
|
||||
fat32.seek(fp_scan, uint32(phoff_s + uint64(pi_s) * uint64(phentsize_s)))
|
||||
fat32.read(fp_scan, &phdr_bufs_s[pi_s], 56)
|
||||
if phdr_bufs_s[pi_s].p_type == PT_INTERP:
|
||||
ilen: uint64 = uint64(phdr_bufs_s[pi_s].p_filesz)
|
||||
if ilen > uint64(255): ilen = uint64(255)
|
||||
if ilen > 0:
|
||||
fat32.seek(fp_scan, uint32(phdr_bufs_s[pi_s].p_offset))
|
||||
ir: int = int(fat32.read(fp_scan, &interp_path[0], uint32(ilen)))
|
||||
if ir > 0:
|
||||
interp_len = ir
|
||||
interp_path[interp_len] = 0
|
||||
if phdr_bufs_s[pi_s].p_type == PT_LOAD:
|
||||
ph_off_s: uint64 = uint64(phdr_bufs_s[pi_s].p_offset)
|
||||
ph_fsz_s: uint64 = uint64(phdr_bufs_s[pi_s].p_filesz)
|
||||
if ehdr_s.e_phoff >= ph_off_s and ehdr_s.e_phoff < ph_off_s + ph_fsz_s:
|
||||
phdr_va = main_base + uint64(phdr_bufs_s[pi_s].p_vaddr) + (uint64(ehdr_s.e_phoff) - ph_off_s)
|
||||
seg_end_s: uint64 = phdr_bufs_s[pi_s].p_vaddr + phdr_bufs_s[pi_s].p_memsz
|
||||
if found_load_s == 0:
|
||||
lowest_vaddr = phdr_bufs_s[pi_s].p_vaddr
|
||||
@@ -431,20 +618,10 @@ def spawn_elf(path: const str) -> int:
|
||||
highest_vaddr_end = seg_end_s
|
||||
fat32.close(fp_scan)
|
||||
|
||||
isolate_va: uint64 = lowest_vaddr
|
||||
isolate_va: uint64 = main_base + lowest_vaddr
|
||||
if isolate_va == 0:
|
||||
isolate_va = 0x400000
|
||||
|
||||
# Load ELF to physical memory WITHOUT mapping in kernel PML4.
|
||||
# do_map=0: load_elf stores mapping info in globals for spawn_elf to use.
|
||||
# ROOT CAUSE FIX: previously load_elf mapped ELF vaddr (e.g. 0x400000) into
|
||||
# kernel PML4, overwriting identity mapping of buddy memory_block headers.
|
||||
entry: rawptr = load_elf(path, 0)
|
||||
if entry is None:
|
||||
serial.puts("[elf] spawn: load failed\n")
|
||||
fat32.unlock()
|
||||
return -1
|
||||
|
||||
# Clone kernel PML4 (identity mapping intact, no ELF mapping yet)
|
||||
proc_pml4: uint64 = paging.clone_for_process(isolate_va)
|
||||
slog1: [80]char
|
||||
@@ -455,21 +632,40 @@ def spawn_elf(path: const str) -> int:
|
||||
fat32.unlock()
|
||||
return -1
|
||||
|
||||
# Map ELF pages in the CLONED PML4 (not kernel's).
|
||||
# This prevents overwriting kernel identity mapping at 0x400000 which
|
||||
# would corrupt buddy system memory_block headers.
|
||||
# Must disable interrupts: @pml4 global is used by MapPage and a timer
|
||||
# interrupt mid-section would call switch_pid, corrupting @pml4.
|
||||
# Load the main image and (if present) the interpreter, mapping each into
|
||||
# the cloned PML4 immediately so the next load can reuse the globals.
|
||||
# Interrupts stay off: @pml4 is global.
|
||||
asm.cli()
|
||||
_kernel_cr3: uint64 = 0
|
||||
__asm__(f"""mov {asmout(_kernel_cr3, ASM_DESCR.OUTPUT_REG)}, cr3""", op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
__asm__(f"mov cr3, {asminp(proc_pml4, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
paging.set_active_pml4(proc_pml4)
|
||||
mi: uint64
|
||||
for mi in range(_last_elf_num_map_pages):
|
||||
_va: uint64 = _last_elf_virt_page_start + mi * 0x1000
|
||||
_pa: uint64 = _last_elf_phys_page_start + mi * 0x1000
|
||||
paging.MapPage(_va, _pa, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER)
|
||||
|
||||
entry: rawptr = load_elf(path, 0, main_base)
|
||||
if entry is None:
|
||||
__asm__(f"mov cr3, {asminp(_kernel_cr3, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
paging.set_active_pml4(_kernel_cr3)
|
||||
asm.sti()
|
||||
serial.puts("[elf] spawn: load failed\n")
|
||||
fat32.unlock()
|
||||
return -1
|
||||
_map_last_elf()
|
||||
|
||||
interp_entry: rawptr = None
|
||||
interp_base: uint64 = uint64(0)
|
||||
if interp_len > 0:
|
||||
interp_base = uint64(0x70000000)
|
||||
interp_entry = load_elf(&interp_path[0], 0, interp_base)
|
||||
if interp_entry is None:
|
||||
__asm__(f"mov cr3, {asminp(_kernel_cr3, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
paging.set_active_pml4(_kernel_cr3)
|
||||
asm.sti()
|
||||
serial.puts("[elf] spawn: interpreter load failed\n")
|
||||
fat32.unlock()
|
||||
return -1
|
||||
_map_last_elf()
|
||||
entry = interp_entry
|
||||
|
||||
__asm__(f"mov cr3, {asminp(_kernel_cr3, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
paging.set_active_pml4(_kernel_cr3)
|
||||
asm.sti()
|
||||
@@ -482,8 +678,14 @@ def spawn_elf(path: const str) -> int:
|
||||
serial.puts("[elf] spawn: create_process failed\n")
|
||||
fat32.unlock()
|
||||
return -2
|
||||
p.elf_base = uint64(entry)
|
||||
p.elf_base = main_base
|
||||
p.pml4_root = proc_pml4
|
||||
p.parent_pid = proc.current()
|
||||
if _build_argv_stack(p, path, argv, envp, phdr_va, main_entry, phent_u, phnum_u, interp_base) != 0:
|
||||
serial.puts("[elf] spawn: startup stack build failed\n")
|
||||
p.exit()
|
||||
fat32.unlock()
|
||||
return -4
|
||||
if p.pid >= 0 and p.pid < MAX_SPAWNED:
|
||||
_elf_entries[p.pid] = entry
|
||||
|
||||
|
||||
+1830
-192
File diff suppressed because it is too large.
Load diff
+27
-4
@@ -111,13 +111,25 @@ class ViperKernel:
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
if BootInfo is not None:
|
||||
paging.init(BootInfo.MemmapAddr, BootInfo.MemmapSize)
|
||||
# PAT: entry 4 = write-combining. The framebuffer is mapped with
|
||||
# PTE_PAT so large compositor blits stay fast on VMware/real HW
|
||||
# (WB is fast but can be incoherent for scanout; UC is slow).
|
||||
feats: uint32 = cpu.detect_features_static()
|
||||
if (feats & uint32(cpu.CPU_FEATURE_PAT)) != 0:
|
||||
pat_v: uint64 = cpu.msr_read(uint32(0x277))
|
||||
pat_v = (pat_v & ~(uint64(7) << uint64(32))) | (uint64(1) << uint64(32))
|
||||
cpu.msr_write(uint32(0x277), pat_v)
|
||||
paging.set_pat_wc()
|
||||
serial.puts("[main] PAT write-combining enabled\n")
|
||||
fb_flags: uint64 = paging.PTE_WRITABLE
|
||||
if paging.has_pat_wc() != 0:
|
||||
fb_flags = fb_flags | paging.PTE_PAT
|
||||
fb_pages: uint64 = (fb_size + uint64(0xFFF)) >> uint64(12)
|
||||
for fpi in range(fb_pages):
|
||||
fva: uint64 = uint64(fb) + fpi * uint64(0x1000)
|
||||
# 帧缓冲改用回写(去掉 PTE_PCD):此前按非缓存映射,每次写屏都直通设备,
|
||||
# 全屏/大块刷新极慢。回写在 QEMU 下显示正确(TCG 不模拟缓存不一致);
|
||||
# 真机若要严格正确应改为 write-combining(走 PAT)。
|
||||
paging.MapPage(fva, fva, paging.PTE_WRITABLE)
|
||||
# 帧缓冲是 MMIO:优先 PAT WC(快且对扫描输出可见);CPU 不支持
|
||||
# PAT 时保持回写,避免用 UC 把整帧提交拖慢。
|
||||
paging.MapPage(fva, fva, fb_flags)
|
||||
mm.init(BootInfo.MemmapAddr, BootInfo.MemmapSize, BootInfo.MemmapDescSize, BootInfo.framebuffer_addr, fb_size, BootInfo.kernel_PhysAddr, _kern_end)
|
||||
pt_pool_buf: rawptr = mm.malloc(uint64(513) * uint64(4096))
|
||||
paging.init_pool(pt_pool_buf, uint64(513) * uint64(4096))
|
||||
@@ -240,6 +252,12 @@ class ViperKernel:
|
||||
viperlib.snprintf(&font_log, 64, "[main] FontDemo spawned as pid=%d\n", font_pid)
|
||||
serial.puts(&font_log)
|
||||
|
||||
img_pid: int = elf.spawn_elf("/APPS/imagesee.elf")
|
||||
if img_pid >= 0:
|
||||
img_log: [64]char
|
||||
viperlib.snprintf(&img_log, 64, "[main] ImageSee spawned as pid=%d\n", img_pid)
|
||||
serial.puts(&img_log)
|
||||
|
||||
sched.Scheduler.enable()
|
||||
|
||||
# 破坏性 FAT32 测试已删除(write/rename/delete/mkdir/rmdir 会永久改坏 disk.img)。
|
||||
@@ -263,7 +281,12 @@ class ViperKernel:
|
||||
# /dev/desk:explorer 与各 app 的共享内存通道(S3)。分配失败就让登记跳过 ——
|
||||
# explorer 的 open 会明确失败并打日志,不做静默兜底。
|
||||
desk_shm: rawptr = mm.malloc(uint64(devfs.DESK_SHM_SIZE))
|
||||
sl: [160]char
|
||||
viperlib.snprintf(&sl, 160, "[main] desk shm=0x%lx size=0x%lx\n",
|
||||
uint64(desk_shm), uint64(devfs.DESK_SHM_SIZE))
|
||||
serial.puts(&sl)
|
||||
devfs.devfs_register_desk(desk_shm, uint64(devfs.DESK_SHM_SIZE))
|
||||
mm.check_lists(1)
|
||||
|
||||
serial.puts("[main] sched started\n")
|
||||
|
||||
|
||||
+192
-6
@@ -56,6 +56,58 @@ class memory_block:
|
||||
# 块头魔数("BLK!")
|
||||
define BLOCK_MAGIC = 0x424C4B21
|
||||
|
||||
# [DIAG] 监视某个空闲块头是否被越界写清零;配合 _last_user 定位"谁写到了这里"。
|
||||
static _watch_addr: uint64 = 0
|
||||
static _last_user: uint64 = 0
|
||||
static _last_size: uint64 = 0
|
||||
static _last_kind: int = 0
|
||||
|
||||
def _watch_check(where: int):
|
||||
global _watch_addr
|
||||
if _watch_addr == 0: return
|
||||
if uint64((memory_block*(_watch_addr)).magic) == uint64(BLOCK_MAGIC): return
|
||||
_wl: [192]char
|
||||
viperlib.snprintf(&_wl, 192, "[mm] WATCH0 at %d: watch=0x%lx magic=0x%lx last=0x%lx size=0x%lx kind=%d\n",
|
||||
where, _watch_addr, uint64(uint32((memory_block*(_watch_addr)).magic)),
|
||||
_last_user, _last_size, _last_kind)
|
||||
serial.puts(&_wl)
|
||||
_watch_addr = 0
|
||||
|
||||
# ---- 真正的 4KiB 匿名页分配器 ----
|
||||
# mmap 的匿名/文件页不再走 malloc(4096)(那会占 8KiB:4KiB 头页 + 4KiB 数据),
|
||||
# 而是直接向 buddy 要 order-0 块,整页交给用户。代价是页首的 memory_block 头会
|
||||
# 被用户数据覆盖 ⇒ free 时必须按"裸页"重建头再 merge;同时用位图区分"裸页"和
|
||||
# 普通块,避免 merge 把已分配裸页的脏头误判成坏块。
|
||||
define MAX_RAW_PAGES = 262144 # 1GiB / 4KiB
|
||||
define RAW_BITS_BYTES = 32768 # MAX_RAW_PAGES / 8
|
||||
static _raw_heap_start: uint64 = 0
|
||||
static _raw_page_count: uint64 = 0
|
||||
static _raw_bits: [RAW_BITS_BYTES]uint8
|
||||
|
||||
def _raw_index(addr: uint64) -> uint64:
|
||||
if addr < _raw_heap_start: return uint64(0xFFFFFFFFFFFFFFFF)
|
||||
off: uint64 = addr - _raw_heap_start
|
||||
idx: uint64 = off >> 12
|
||||
if idx >= _raw_page_count: return uint64(0xFFFFFFFFFFFFFFFF)
|
||||
return idx
|
||||
|
||||
def _raw_is_used(addr: uint64) -> int:
|
||||
idx: uint64 = _raw_index(addr)
|
||||
if idx == uint64(0xFFFFFFFFFFFFFFFF): return 0
|
||||
return (int(_raw_bits[idx >> 3]) >> int(idx & 7)) & 1
|
||||
|
||||
def _raw_set(addr: uint64, used: int):
|
||||
idx: uint64 = _raw_index(addr)
|
||||
if idx == uint64(0xFFFFFFFFFFFFFFFF): return
|
||||
byte_i: uint64 = idx >> 3
|
||||
bit_i: int = int(idx & 7)
|
||||
b: int = int(_raw_bits[byte_i])
|
||||
if used != 0:
|
||||
b = b | (1 << bit_i)
|
||||
else:
|
||||
b = b & (255 - (1 << bit_i))
|
||||
_raw_bits[byte_i] = uint8(b & 0xFF)
|
||||
|
||||
# 伙伴系统结构
|
||||
class __buddy_system:
|
||||
free_lists: [MAX_ORDER + 1]memory_block*
|
||||
@@ -91,11 +143,17 @@ class slab_page:
|
||||
def align_4k(addr: uint64) -> uint64:
|
||||
return (addr + 0xFFF) & ~0xFFF
|
||||
|
||||
# 计算阶数
|
||||
# 计算阶数:必须**向上取整**。旧实现用逐个右移(floor):
|
||||
# size=0x4498+4096=0x5498? 或 _bb 的 1141760+4096 → 实际拿到 1MB 块,
|
||||
# 但用户区从 block+4KB 起,写满 1.09MB 就**越过块尾 92KB**,踩坏后继块头 /
|
||||
# 别人的数据(现场:[mm] BAD blk blk=0x21780000 sz=0 magic=0、随内存大小
|
||||
# 出现的随机崩溃、以及 Terminal 的 Text 缓冲被踩后键盘无响应)。
|
||||
# 向上取整后 1.14MB 请求拿 2MB 块,32MB 共享区拿 64MB 块(多一页头)。
|
||||
static def get_order(size: uint64) -> int:
|
||||
order: int = 0
|
||||
while size > MIN_BLOCK_SIZE:
|
||||
size >>= 1
|
||||
block: uint64 = uint64(MIN_BLOCK_SIZE)
|
||||
while block < size and order < MAX_ORDER:
|
||||
block = block << 1
|
||||
order += 1
|
||||
return order
|
||||
|
||||
@@ -214,6 +272,10 @@ class _BuddySystemObject:
|
||||
# 检查伙伴块是否存在且空闲
|
||||
if buddy >= memory_block*(self.buddy.end_addr):
|
||||
break # 伙伴块超出内存范围
|
||||
# 裸页(mmap 匿名/文件页)的块头已被用户数据覆盖:位图标记为已分配,
|
||||
# 绝不能读它的 state/magic(会把用户数据当块头,误报 BAD / 误合并)。
|
||||
if _raw_is_used(uint64(buddy)) != 0:
|
||||
break
|
||||
if int(buddy.state) != BLOCK_FREE or int(buddy.order) != order:
|
||||
break # 伙伴块不空闲或阶数不匹配
|
||||
# 加固:摘链前先校验伙伴块头(magic / size-order 一致 / prev-next 在堆内且对齐)。
|
||||
@@ -260,8 +322,31 @@ class _BuddySystemObject:
|
||||
# 保持空闲链原样 —— 否则紧接着的 `block.next.prev = None` 就会踩坏值。
|
||||
_arc: int = self._check_block(block, BLOCK_FREE)
|
||||
if _arc != 0:
|
||||
self._report_bad_block(1, _arc, block, found_order)
|
||||
return None
|
||||
# 现场:1GiB 内存布局下 order-16 空闲块头被整块清零(sz/st/ord/magic/
|
||||
# nxt/prv 全 0),块体本身未分配。若整头全 0,可按链表阶数无损重建头,
|
||||
# 否则一次坏块会让之后所有分配永久失败(用户态 malloc 返回 NULL →
|
||||
# 写 0 触发 [0E])。重建只针对"全零头",其他损坏仍按原逻辑拒绝。
|
||||
if _arc == 1:
|
||||
_zeroed: int = 0
|
||||
if block.size == 0 and block.state == 0 and block.order == 0 and uint64(block.next) == 0 and uint64(block.prev) == 0:
|
||||
_zeroed = 1
|
||||
if _zeroed == 1:
|
||||
block.size = get_block_size(found_order)
|
||||
block.state = BLOCK_FREE
|
||||
block.order = uint8(found_order)
|
||||
block.magic = uint32(BLOCK_MAGIC)
|
||||
block.next = None
|
||||
block.prev = None
|
||||
_rbl: [128]char
|
||||
viperlib.snprintf(&_rbl, 128, "[mm] repaired zeroed free block 0x%lx order=%d size=0x%lx\n",
|
||||
uint64(block), found_order, block.size)
|
||||
serial.puts(&_rbl)
|
||||
else:
|
||||
self._report_bad_block(1, _arc, block, found_order)
|
||||
return None
|
||||
else:
|
||||
self._report_bad_block(1, _arc, block, found_order)
|
||||
return None
|
||||
# 从空闲链表中移除块
|
||||
if block.next: block.next.prev = None
|
||||
self.buddy.free_lists[found_order] = block.next
|
||||
@@ -502,6 +587,8 @@ SlabAllocator: _SlabAllocator
|
||||
|
||||
# 内存分配函数
|
||||
export def malloc(size: uint64) -> rawptr:
|
||||
global _last_user, _last_size, _last_kind
|
||||
_watch_check(1)
|
||||
if size == 0: return None
|
||||
if size <= SLAB_THRESHOLD:
|
||||
sc: int = _slab_size_class(size)
|
||||
@@ -512,6 +599,9 @@ export def malloc(size: uint64) -> rawptr:
|
||||
_mb: [96]char
|
||||
viperlib.snprintf(&_mb, 96, "[mm] malloc(%lu) slab sc=%d -> NULL\n", size, sc)
|
||||
serial.puts(&_mb)
|
||||
_last_user = uint64(result)
|
||||
_last_size = size
|
||||
_last_kind = 1
|
||||
return result
|
||||
hdr: uint64 = memory_block.__sizeof__()
|
||||
if size >= 4096:
|
||||
@@ -522,6 +612,9 @@ export def malloc(size: uint64) -> rawptr:
|
||||
block: memory_block* = BuddySystemObject.allocate_block(total)
|
||||
if not block: return None
|
||||
user_ptr: uint64 = (uint64(block) + hdr + 4095) & ~uint64(4095)
|
||||
_last_user = user_ptr
|
||||
_last_size = size
|
||||
_last_kind = 3
|
||||
real_hdr: uint64 = user_ptr - 8
|
||||
__asm__(f"""mov rax, {asminp(block, ASM_DESCR.REG_ANY)}
|
||||
mov [{asminp(rawptr(real_hdr), ASM_DESCR.REG_ANY)}], rax""",
|
||||
@@ -533,6 +626,9 @@ export def malloc(size: uint64) -> rawptr:
|
||||
if order2 > MAX_ORDER: return None
|
||||
block2: memory_block* = BuddySystemObject.allocate_block(total2)
|
||||
if not block2: return None
|
||||
_last_user = uint64(block2) + hdr
|
||||
_last_size = size
|
||||
_last_kind = 2
|
||||
return rawptr(uint64(block2) + hdr)
|
||||
|
||||
|
||||
@@ -549,6 +645,7 @@ export def malloc_direct(size: uint64) -> rawptr:
|
||||
|
||||
|
||||
export def free(ptr: rawptr):
|
||||
_watch_check(2)
|
||||
if ptr is None: return
|
||||
sp: slab_page* = _slab_find_page(ptr)
|
||||
if sp:
|
||||
@@ -575,6 +672,67 @@ export def free(ptr: rawptr):
|
||||
block2: memory_block* = rawptr(uint64(ptr) - memory_block.__sizeof__())
|
||||
BuddySystemObject.free_block(block2)
|
||||
|
||||
# 真正的 4KiB 页分配:整页归调用者,页首不再保留块头。
|
||||
# ---- 4KiB 页池:mmap/brk/fork 的页不再逐页进出 buddy ----
|
||||
# 旧方案 alloc_page() 直接向 buddy 要 order-0 块:用户写满整页会覆盖页首块头,
|
||||
# 每次 free 都得重建头再 merge;一旦 free-list 有重复/陈旧表头,就会出现
|
||||
# "页已经分给字体缓冲,同时还挂在空闲链上" —— 字体数据直接把块头写成 ASCII
|
||||
# (BAD blk ... magic=0x574d6f74)。这里改成独立页池:
|
||||
# · 每 2MiB 向 buddy 申请一个 order-9 块(首 4KiB 留给块头,不发给用户)
|
||||
# · 剩余 511 页串成页内 free-list,alloc/free 只改页首 8 字节
|
||||
# · 页永远不逐页归还 buddy,因此页数据永远不会碰到 buddy 块头/空闲链
|
||||
# 代价:每 2MiB 浪费 4KiB(0.2%),且不再把空闲页合并回 buddy。
|
||||
define PAGE_POOL_CHUNK_SIZE = 0x200000
|
||||
define PAGE_POOL_RESERVE = 0x1000
|
||||
define PAGE_POOL_MAX = 134217728 # 128MiB
|
||||
static _page_free_head: rawptr = None
|
||||
|
||||
def _pg_next(p: rawptr) -> rawptr:
|
||||
return rawptr(*(uint64*(p)))
|
||||
|
||||
def _pg_set_next(p: rawptr, n: rawptr):
|
||||
*(uint64*(p)) = uint64(n)
|
||||
|
||||
# 启动时一次性预留一大块给页池,之后再不向 buddy 要块。这样页池的块永远不会
|
||||
# 出现在 buddy 空闲链上,页数据也不可能踩 buddy 块头;页分配/释放只操作页内
|
||||
# free-list(页首 8 字节)。预留块首 4KiB 保留给 memory_block 头。
|
||||
def init_page_pool():
|
||||
global _page_free_head
|
||||
if _page_free_head is not None: return
|
||||
b: memory_block* = BuddySystemObject.allocate_block(uint64(PAGE_POOL_MAX))
|
||||
if not b:
|
||||
b = BuddySystemObject.allocate_block(uint64(67108864)) # 64MiB
|
||||
if not b:
|
||||
b = BuddySystemObject.allocate_block(uint64(33554432)) # 32MiB
|
||||
if not b:
|
||||
b = BuddySystemObject.allocate_block(uint64(16777216)) # 16MiB
|
||||
if not b: return
|
||||
base: uint64 = uint64(b)
|
||||
end: uint64 = base + b.size
|
||||
first: uint64 = base + uint64(PAGE_POOL_RESERVE)
|
||||
p: uint64 = first
|
||||
while p < end:
|
||||
nxt: uint64 = 0
|
||||
if p + uint64(4096) < end: nxt = p + uint64(4096)
|
||||
_pg_set_next(rawptr(p), rawptr(nxt))
|
||||
p = p + uint64(4096)
|
||||
_page_free_head = rawptr(first)
|
||||
|
||||
export def alloc_page() -> rawptr:
|
||||
global _page_free_head
|
||||
if _page_free_head is None: return None
|
||||
p: rawptr = _page_free_head
|
||||
_page_free_head = _pg_next(p)
|
||||
return p
|
||||
|
||||
# 页池释放:只把页压回页内 free-list,不碰 buddy。
|
||||
export def free_page(ptr: rawptr):
|
||||
global _page_free_head
|
||||
if ptr is None: return
|
||||
a: uint64 = uint64(ptr) & ~uint64(0xFFF)
|
||||
_pg_set_next(rawptr(a), _page_free_head)
|
||||
_page_free_head = rawptr(a)
|
||||
|
||||
export def calloc(nmemb: uint64, size: uint64) -> rawptr:
|
||||
total_size: uint64 = nmemb * size
|
||||
ptr: rawptr = malloc(total_size)
|
||||
@@ -620,7 +778,7 @@ def free_memory_regions():
|
||||
|
||||
# 初始化内存管理系统
|
||||
def init(MemmapAddr: uint64, MemmapSize: uint64, MemmapDescSize: uint64, FbAddr: uint64 = 0, FbSize: uint64 = 0, KernelAddr: uint64 = 0, KernelEnd: uint64 = 0):
|
||||
global BuddySystemObject
|
||||
global BuddySystemObject, _raw_heap_start, _raw_page_count
|
||||
start_addr: rawptr = None
|
||||
size: uint64 = 0
|
||||
if MemmapAddr != 0:
|
||||
@@ -703,7 +861,35 @@ def init(MemmapAddr: uint64, MemmapSize: uint64, MemmapDescSize: uint64, FbAddr:
|
||||
if size < min_blk2:
|
||||
size = 33554432
|
||||
BuddySystemObject.__init__(start_addr, size)
|
||||
_raw_heap_start = uint64(start_addr)
|
||||
_raw_page_count = size >> 12
|
||||
if _raw_page_count > uint64(MAX_RAW_PAGES): _raw_page_count = uint64(MAX_RAW_PAGES)
|
||||
init_page_pool()
|
||||
SlabAllocator.__init__()
|
||||
_hl: [128]char
|
||||
viperlib.snprintf(&_hl, 128, "[mm] heap start=0x%lx end=0x%lx size=0x%lx\n",
|
||||
uint64(start_addr), uint64(start_addr) + size, size)
|
||||
serial.puts(&_hl)
|
||||
check_lists(0)
|
||||
|
||||
# 空闲链头一致性检查:tag 只用于日志定位调用阶段。
|
||||
export def check_lists(tag: int):
|
||||
i: int = 0
|
||||
bad: int = 0
|
||||
while i <= MAX_ORDER:
|
||||
b: memory_block* = BuddySystemObject.buddy.free_lists[i]
|
||||
if b is not None:
|
||||
if uint64(b.magic) != uint64(BLOCK_MAGIC):
|
||||
_cl: [128]char
|
||||
viperlib.snprintf(&_cl, 128, "[mm] check%d order=%d bad head=0x%lx magic=0x%lx\n",
|
||||
tag, i, uint64(b), uint64(uint32(b.magic)))
|
||||
serial.puts(&_cl)
|
||||
bad += 1
|
||||
if bad >= 3: return
|
||||
i += 1
|
||||
_ok: [64]char
|
||||
viperlib.snprintf(&_ok, 64, "[mm] check%d free-list heads ok\n", tag)
|
||||
serial.puts(&_ok)
|
||||
|
||||
|
||||
# 虚拟内存映射结构
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import drivers.serial.uart.serial as serial
|
||||
import viperlib
|
||||
import string
|
||||
|
||||
|
||||
define PTE_PRESENT = (1 << 0)
|
||||
@@ -11,6 +12,17 @@ define PTE_ACCESSED = (1 << 5)
|
||||
define PTE_DIRTY = (1 << 6)
|
||||
define PTE_PS = (1 << 7)
|
||||
define PTE_GLOBAL = (1 << 8)
|
||||
# 4KB PTE bit 7 = PAT select (for large pages the same bit is PS).
|
||||
define PTE_PAT = (1 << 7)
|
||||
|
||||
# PAT entry 4 is reprogrammed to write-combining (WC) for the framebuffer.
|
||||
# IA32_PAT is configured in main.vp after CPUID tells us PAT is available.
|
||||
_pat_wc: int = 0
|
||||
def set_pat_wc():
|
||||
global _pat_wc
|
||||
_pat_wc = 1
|
||||
def has_pat_wc() -> int:
|
||||
return _pat_wc
|
||||
|
||||
class _p:
|
||||
entries: [512]uint64
|
||||
@@ -138,17 +150,45 @@ def _split_1gb_to_pd(pdp_pa: uint64, pdp_idx: uint64) -> int:
|
||||
if not (e & PTE_PRESENT): return 1
|
||||
if not (e & PTE_PS): return 1
|
||||
base: uint64 = e & ~uint64(0x3FFFFFFF)
|
||||
inherit: uint64 = e & (PTE_PCD | PTE_PWT | PTE_USER | uint64(0x8000000000000000))
|
||||
# 只继承缓存/执行属性,**绝不继承 PTE_USER**:这些 2MB 条目是 identity 映射,
|
||||
# 用户态只能通过 MapPage 为具体 4KB 页显式加 USER。若把固件 1GB 条目的 U=1
|
||||
# 带进 2MB 叶条目,应用一旦越界写到尚未映射的 VA(例如 heap 0x21780000),
|
||||
# 就会命中 identity 叶页、直接改物理内存里的 buddy 块头(BAD blk 源头)。
|
||||
inherit: uint64 = e & (PTE_PCD | PTE_PWT | uint64(0x8000000000000000))
|
||||
new_pd: _p* = AllocPage()
|
||||
if not new_pd: return 0
|
||||
for si in range(512):
|
||||
new_pd.entries[si] = (base + uint64(si) * uint64(0x200000)) | PTE_PRESENT | PTE_WRITABLE | PTE_PS | inherit
|
||||
# 新 PDPTE 是**非叶条目**:只带 PRESENT|WRITABLE|USER,**绝不带 bit63(NX)** ——
|
||||
# NX 生效位置是叶条目,已由上面 512 个 2MB 条目继承;非叶条目带 bit63 会让后续所有
|
||||
# `entry & ~0xFFF` 派生出的表指针变成非规范地址(正是刚修的 #GP 根因)。
|
||||
pdp_t.entries[pdp_idx] = (uint64(new_pd) & ~uint64(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (inherit & PTE_USER)
|
||||
# 新 PDPTE 是**非叶条目**:只带 PRESENT|WRITABLE;USER 由 MapPage 在真正建立
|
||||
# 用户页时逐级补上。非叶条目也不能带 bit63(NX)(见下)。
|
||||
pdp_t.entries[pdp_idx] = (uint64(new_pd) & ~uint64(0xFFF)) | PTE_PRESENT | PTE_WRITABLE
|
||||
return 1
|
||||
|
||||
# 把 1GiB 范围内"物理地址 == 虚拟地址"的 identity 叶条目的 USER 位清掉。
|
||||
# 参数 gb_base 必须是 1GiB 对齐的虚拟基址(pdp_idx << 30)。
|
||||
def _sanitize_identity_pd(pd: _p*, gb_base: uint64):
|
||||
i: int = 0
|
||||
while i < 512:
|
||||
e: uint64 = pd.entries[i]
|
||||
if (e & PTE_PRESENT) == 0:
|
||||
i += 1
|
||||
continue
|
||||
va2m: uint64 = gb_base + uint64(i) * uint64(0x200000)
|
||||
if (e & PTE_PS) != 0:
|
||||
if (e & ~uint64(0x1FFFFF)) == va2m and (e & PTE_USER) != 0:
|
||||
pd.entries[i] = e & ~PTE_USER
|
||||
else:
|
||||
pt_s: _p* = _p*(e & ~uint64(0xFFF))
|
||||
j: int = 0
|
||||
while j < 512:
|
||||
ee: uint64 = pt_s.entries[j]
|
||||
if (ee & PTE_PRESENT) != 0 and (ee & PTE_USER) != 0:
|
||||
va4k: uint64 = va2m + uint64(j) * uint64(0x1000)
|
||||
if (ee & ~uint64(0xFFF)) == va4k:
|
||||
pt_s.entries[j] = ee & ~PTE_USER
|
||||
j += 1
|
||||
i += 1
|
||||
|
||||
def MapPage(VirtAddr: uint64, PhysAddr: uint64, flags: uint64):
|
||||
global pml4, pdp, pd, pt
|
||||
if VirtAddr == 0 or PhysAddr == 0: return
|
||||
@@ -274,6 +314,68 @@ def EnablePaging():
|
||||
global pml4, pdp, pd, pt
|
||||
__asm__(f"mov cr3, {asminp(uint64(pml4), ASM_DESCR.REG_ANY)}")
|
||||
|
||||
# Physical address backing a 4KB user page, or 0 if unmapped / a huge page.
|
||||
# munmap() needs this before UnMapPage() clears the PTE, so it can hand the
|
||||
# frame back to the buddy allocator.
|
||||
# (Local helper: _table_phys is defined later in this module and Viper does
|
||||
# not allow same-module forward references.)
|
||||
def _entry_table(e: uint64) -> uint64:
|
||||
if (e & PTE_PRESENT) == 0: return 0
|
||||
if (e & uint64(0x7FF0000000000000)) != 0: return 0
|
||||
return e & uint64(0x000FFFFFFFFFF000)
|
||||
|
||||
def GetPagePhys(VirtAddr: uint64) -> uint64:
|
||||
if VirtAddr == 0: return 0
|
||||
p4: _p* = pml4
|
||||
if not p4: return 0
|
||||
va: uint64 = VirtAddr & ~uint64(0xFFF)
|
||||
i4: uint64 = (va >> 39) & 0x1FF
|
||||
pdp_pa: uint64 = _entry_table(p4.entries[i4])
|
||||
if pdp_pa == 0: return 0
|
||||
p3: _p* = _p*(pdp_pa)
|
||||
i3: uint64 = (va >> 30) & 0x1FF
|
||||
if p3.entries[i3] & PTE_PS: return 0
|
||||
pd_pa: uint64 = _entry_table(p3.entries[i3])
|
||||
if pd_pa == 0: return 0
|
||||
p2: _p* = _p*(pd_pa)
|
||||
i2: uint64 = (va >> 21) & 0x1FF
|
||||
if p2.entries[i2] & PTE_PS: return 0
|
||||
pt_pa: uint64 = _entry_table(p2.entries[i2])
|
||||
if pt_pa == 0: return 0
|
||||
p1: _p* = _p*(pt_pa)
|
||||
i1: uint64 = (va >> 12) & 0x1FF
|
||||
e: uint64 = p1.entries[i1]
|
||||
if not (e & PTE_PRESENT): return 0
|
||||
return e & uint64(0x000FFFFFFFFFF000)
|
||||
|
||||
# Rewrite the protection bits of an existing 4KB mapping, keeping the frame.
|
||||
# Returns 1 on success, 0 if the page is not a present 4KB page.
|
||||
def SetPageFlags(VirtAddr: uint64, flags: uint64) -> int:
|
||||
if VirtAddr == 0: return 0
|
||||
p4: _p* = pml4
|
||||
if not p4: return 0
|
||||
va: uint64 = VirtAddr & ~uint64(0xFFF)
|
||||
i4: uint64 = (va >> 39) & 0x1FF
|
||||
pdp_pa: uint64 = _entry_table(p4.entries[i4])
|
||||
if pdp_pa == 0: return 0
|
||||
p3: _p* = _p*(pdp_pa)
|
||||
i3: uint64 = (va >> 30) & 0x1FF
|
||||
if p3.entries[i3] & PTE_PS: return 0
|
||||
pd_pa: uint64 = _entry_table(p3.entries[i3])
|
||||
if pd_pa == 0: return 0
|
||||
p2: _p* = _p*(pd_pa)
|
||||
i2: uint64 = (va >> 21) & 0x1FF
|
||||
if p2.entries[i2] & PTE_PS: return 0
|
||||
pt_pa: uint64 = _entry_table(p2.entries[i2])
|
||||
if pt_pa == 0: return 0
|
||||
p1: _p* = _p*(pt_pa)
|
||||
i1: uint64 = (va >> 12) & 0x1FF
|
||||
e: uint64 = p1.entries[i1]
|
||||
if not (e & PTE_PRESENT): return 0
|
||||
p1.entries[i1] = (e & uint64(0x000FFFFFFFFFF000)) | flags | PTE_PRESENT
|
||||
__asm__(f"invlpg [{asminp(va, ASM_DESCR.REG_ANY)}]")
|
||||
return 1
|
||||
|
||||
# Extract the physical address of a non-leaf page-table entry.
|
||||
# Bits 12..51 hold the address; bits 52..62 are reserved and must be zero.
|
||||
# Bit 63 (NX) is ignored for non-leaf entries. Returns 0 for unusable entries.
|
||||
@@ -505,7 +607,11 @@ def clone_for_process(isolate_vaddr: uint64) -> uint64:
|
||||
if not new_pdp_page: return uint64(new_pml4_page)
|
||||
for i in range(512):
|
||||
new_pdp_page.entries[i] = kernel_pdp.entries[i]
|
||||
new_pml4_page.entries[pml4_idx] = (uint64(new_pdp_page) & ~uint64(0xFFF)) | (pml4.entries[pml4_idx] & uint64(0xFFF))
|
||||
# 固件 identity 大页可能带 U=1:克隆到进程地址空间后必须降为 supervisor。
|
||||
# 用户页的 USER 位由后续 MapPage 在具体 4KB PTE 上显式添加。
|
||||
if (new_pdp_page.entries[i] & PTE_PRESENT) != 0 and (new_pdp_page.entries[i] & PTE_PS) != 0:
|
||||
new_pdp_page.entries[i] = new_pdp_page.entries[i] & ~PTE_USER
|
||||
new_pml4_page.entries[pml4_idx] = (uint64(new_pdp_page) & ~uint64(0xFFF)) | (pml4.entries[pml4_idx] & uint64(0xFFF) & ~PTE_USER)
|
||||
pdp_idx: uint64 = (isolate_vaddr >> 30) & 0x1FF
|
||||
if not (kernel_pdp.entries[pdp_idx] & PTE_PRESENT): return uint64(new_pml4_page)
|
||||
# 1GB 大页(PDPTE.PS=1):源端没有下层 PD。先把它拆成常规 PD(语义不变:512 个 2MB
|
||||
@@ -515,11 +621,19 @@ def clone_for_process(isolate_vaddr: uint64) -> uint64:
|
||||
if kernel_pdp.entries[pdp_idx] & PTE_PS:
|
||||
if _split_1gb_to_pd(uint64(kernel_pdp), pdp_idx) == 0: return uint64(new_pml4_page)
|
||||
kernel_pd: _p* = _p*(kernel_pdp.entries[pdp_idx] & ~uint64(0xFFF))
|
||||
# 固件 identity 映射可能把 4KB/2MB 叶条目的 U 置 1。只要 U=1,用户态一旦
|
||||
# 写到尚未显式映射的 VA(应用越界写),就会直接落到**同数值物理页**上——
|
||||
# buddy 块头、页表、别人的数据全会被踩(现场:1GiB 下 0x21780000 的
|
||||
# order-16 空闲块头被清零)。这里把该 1GiB 内"物理地址 == 虚拟地址"的
|
||||
# identity 叶条目降为 supervisor;真正的用户页(phys != va)保持原样。
|
||||
_sanitize_identity_pd(kernel_pd, isolate_vaddr & ~uint64(0x3FFFFFFF))
|
||||
new_pd_page: _p* = AllocPage()
|
||||
if not new_pd_page: return uint64(new_pml4_page)
|
||||
for i in range(512):
|
||||
new_pd_page.entries[i] = kernel_pd.entries[i]
|
||||
new_pdp_page.entries[pdp_idx] = (uint64(new_pd_page) & ~uint64(0xFFF)) | (kernel_pdp.entries[pdp_idx] & uint64(0xFFF))
|
||||
if (new_pd_page.entries[i] & PTE_PRESENT) != 0 and (new_pd_page.entries[i] & PTE_PS) != 0:
|
||||
new_pd_page.entries[i] = new_pd_page.entries[i] & ~PTE_USER
|
||||
new_pdp_page.entries[pdp_idx] = (uint64(new_pd_page) & ~uint64(0xFFF)) | (kernel_pdp.entries[pdp_idx] & uint64(0xFFF) & ~PTE_USER)
|
||||
pd_idx: uint64 = (isolate_vaddr >> 21) & 0x1FF
|
||||
if not (kernel_pd.entries[pd_idx] & PTE_PRESENT): return uint64(new_pml4_page)
|
||||
if kernel_pd.entries[pd_idx] & PTE_PS: return uint64(new_pml4_page)
|
||||
@@ -527,10 +641,88 @@ def clone_for_process(isolate_vaddr: uint64) -> uint64:
|
||||
new_pt_page: _p* = AllocPage()
|
||||
if not new_pt_page: return uint64(new_pml4_page)
|
||||
for i in range(512):
|
||||
new_pt_page.entries[i] = kernel_pt.entries[i]
|
||||
new_pd_page.entries[pd_idx] = (uint64(new_pt_page) & ~uint64(0xFFF)) | (kernel_pd.entries[pd_idx] & uint64(0xFFF))
|
||||
new_pt_page.entries[i] = kernel_pt.entries[i] & ~PTE_USER
|
||||
new_pd_page.entries[pd_idx] = (uint64(new_pt_page) & ~uint64(0xFFF)) | (kernel_pd.entries[pd_idx] & uint64(0xFFF) & ~PTE_USER)
|
||||
return uint64(new_pml4_page)
|
||||
|
||||
# Deep-copy every USER mapping from src into dst (dst must already contain the
|
||||
# kernel mappings, e.g. from create_process_page_table()). Data frames are
|
||||
# allocated through alloc_fn(4096); page-table pages come from the paging pool.
|
||||
#
|
||||
# A table is only duplicated when its subtree really contains user pages;
|
||||
# otherwise the parent's table is shared. Without that check a single user
|
||||
# page at 0x400000 drags the whole kernel identity map (one PT page per 2MB
|
||||
# region) into the new address space and exhausts the page-table pool.
|
||||
def _alloc_user_frame(alloc_fn: rawptr) -> rawptr:
|
||||
newp: rawptr = None
|
||||
__asm__(f"""mov r11, {asminp(alloc_fn, ASM_DESCR.REG_ANY)}
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
and rsp, -16
|
||||
mov rdi, 4096
|
||||
call r11
|
||||
mov rsp, rbp
|
||||
pop rbp
|
||||
mov {asmout(newp, ASM_DESCR.OUTPUT_REG)}, rax""",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX,
|
||||
ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX,
|
||||
ASM_DESCR.CLOBBER_RSI, ASM_DESCR.CLOBBER_RDI,
|
||||
ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9,
|
||||
ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11])
|
||||
return newp
|
||||
|
||||
# level: 1 = PT, 2 = PD, 3 = PDPT (4 = PML4 only used as the entry point).
|
||||
def _table_has_user(t: _p*, level: int) -> int:
|
||||
i: int = 0
|
||||
while i < 512:
|
||||
e: uint64 = t.entries[i]
|
||||
if (e & PTE_PRESENT) != 0:
|
||||
if level == 1:
|
||||
if (e & PTE_USER) != 0: return 1
|
||||
elif (e & PTE_PS) != 0:
|
||||
if (e & PTE_USER) != 0: return 1
|
||||
elif (e & PTE_USER) != 0:
|
||||
if _table_has_user(_p*(e & ~uint64(0xFFF)), level - 1) != 0: return 1
|
||||
i += 1
|
||||
return 0
|
||||
|
||||
def _copy_user_table(src: _p*, dst: _p*, level: int, alloc_fn: rawptr) -> int:
|
||||
i: int = 0
|
||||
while i < 512:
|
||||
e: uint64 = src.entries[i]
|
||||
if (e & PTE_PRESENT) == 0:
|
||||
dst.entries[i] = e
|
||||
elif level == 1:
|
||||
if (e & PTE_USER) == 0:
|
||||
dst.entries[i] = e
|
||||
else:
|
||||
src_phys: uint64 = e & uint64(0x000FFFFFFFFFF000)
|
||||
newp: rawptr = _alloc_user_frame(alloc_fn)
|
||||
if newp is None: return -1
|
||||
string.memcpy(newp, rawptr(src_phys), 4096)
|
||||
dst.entries[i] = (uint64(newp) & ~uint64(0xFFF)) | (e & uint64(0xFFF))
|
||||
elif (e & PTE_PS) != 0:
|
||||
dst.entries[i] = e
|
||||
elif (e & PTE_USER) == 0:
|
||||
dst.entries[i] = e
|
||||
else:
|
||||
csrc: _p* = _p*(e & ~uint64(0xFFF))
|
||||
if _table_has_user(csrc, level - 1) == 0:
|
||||
dst.entries[i] = e
|
||||
else:
|
||||
np: _p* = AllocPage()
|
||||
if not np: return -1
|
||||
dst.entries[i] = (uint64(np) & ~uint64(0xFFF)) | (e & uint64(0xFFF))
|
||||
if _copy_user_table(csrc, np, level - 1, alloc_fn) != 0: return -1
|
||||
i += 1
|
||||
return 0
|
||||
|
||||
def CopyUserSpace(src_pa: uint64, dst_pa: uint64, alloc_fn: rawptr) -> int:
|
||||
if src_pa == 0 or dst_pa == 0: return -1
|
||||
s4: _p* = _p*(src_pa)
|
||||
d4: _p* = _p*(dst_pa)
|
||||
return _copy_user_table(s4, d4, 4, alloc_fn)
|
||||
|
||||
def save_pt_range(vaddr_start: uint64, vaddr_end: uint64, save_buf: uint64*, save_count: uint32*) -> int:
|
||||
global pml4
|
||||
if not pml4: return -1
|
||||
|
||||
@@ -12,15 +12,21 @@ define PROC_READY = 0
|
||||
define PROC_RUNNING = 1
|
||||
define PROC_BLOCKED = 2
|
||||
define PROC_DONE = 3
|
||||
define MAX_PROCESSES = 8
|
||||
define MAX_PROCESSES = 32
|
||||
define MAX_PROC_THREADS = 8
|
||||
define PROC_MAX_FDS = 16
|
||||
# fd 种类(process.fd_kind[])。定义放这里、由 intr/syscall.vp 以 process.FD_KIND_* 引用,
|
||||
# 这样 process.vp 不必 import devfs —— devfs → keyboard/mouse → intr.idt → sched → process
|
||||
# 会形成环,不能让 process 反向依赖 devfs。
|
||||
define FD_KIND_FAT32 = 0
|
||||
define FD_KIND_DEVFS = 1
|
||||
define FD_KIND_DEVFS = 1
|
||||
define FD_KIND_DIR = 2
|
||||
define FD_KIND_PIPE = 3
|
||||
define PROC_MAX_DIRS = 8
|
||||
define MAX_VMAS = 64
|
||||
define VMA_KIND_ANON = 1
|
||||
define VMA_KIND_DEV = 2
|
||||
define VMA_KIND_FILE = 3
|
||||
define KERN_STACK_SIZE = 8192
|
||||
define USER_STACK_SIZE = 65536
|
||||
# 用户栈虚拟区:主线程栈顶 = USER_STACK_VTOP(见 execrunner/elf.vp 的映射),
|
||||
@@ -42,17 +48,39 @@ class Process:
|
||||
heap_start: uint64
|
||||
heap_size: uint64
|
||||
name: [32]char
|
||||
cwd: [256]char
|
||||
fd_table: [PROC_MAX_FDS]rawptr
|
||||
fd_used: [PROC_MAX_FDS]uint8
|
||||
# 0 = FAT32 文件(fd_table[i] 里是 fileobj*);1 = devfs 设备(fd_table[i] 里是
|
||||
# devfs_fd+1,+1 只为避开"devfs fd 0"与空指针的歧义)。READ/WRITE/CLOSE/IOCTL 与
|
||||
# Process.exit 都必须按它分派 —— 把 devfs fd 当 fileobj* 解引用会直接跑飞。
|
||||
fd_kind: [PROC_MAX_FDS]uint8
|
||||
fd_flags: [PROC_MAX_FDS]uint32
|
||||
fd_shared: [PROC_MAX_FDS]uint8
|
||||
dir_table: [PROC_MAX_DIRS]rawptr
|
||||
dir_used: [PROC_MAX_DIRS]uint8
|
||||
dir_pool: [PROC_MAX_DIRS]fat32_types.dirobj
|
||||
kern_stack: uint64
|
||||
user_stack: uint64
|
||||
# Virtual memory areas backing mmap()/mprotect()/munmap()/mremap().
|
||||
vma_base: [MAX_VMAS]uint64
|
||||
vma_size: [MAX_VMAS]uint64
|
||||
vma_prot: [MAX_VMAS]uint32
|
||||
vma_kind: [MAX_VMAS]uint8
|
||||
vma_flags: [MAX_VMAS]uint32
|
||||
# brk() region (separate from the mmap bump allocator).
|
||||
brk_start: uint64
|
||||
brk_end: uint64
|
||||
# Linux process metadata.
|
||||
entry_rsp: uint64
|
||||
parent_pid: int
|
||||
exit_code: int
|
||||
# Signals (per process; delivery happens on syscall return).
|
||||
sig_handler: [65]uint64
|
||||
sig_actflags: [65]uint64
|
||||
sig_restorer: [65]uint64
|
||||
sig_mask: uint64
|
||||
sig_pending: uint64
|
||||
|
||||
def addThread(self, tid: int) -> int:
|
||||
if self.thread_count >= MAX_PROC_THREADS: return -1
|
||||
@@ -69,11 +97,12 @@ class Process:
|
||||
# 当指针解引用而跑飞。devfs 的 fd 表是独立的另一张;正规退出路径本来就不
|
||||
# 回收 fd(见 intr/syscall.vp 的 EXIT 不调 Process.exit),这里只清槽位,
|
||||
# app 自己调 CLOSE 时才走 devfs_close。
|
||||
if fp and self.fd_kind[i] == FD_KIND_FAT32:
|
||||
if fp and self.fd_kind[i] == FD_KIND_FAT32 and self.fd_shared[i] == 0:
|
||||
fat32.close(fp)
|
||||
self.fd_used[i] = 0
|
||||
self.fd_table[i] = None
|
||||
self.fd_kind[i] = 0
|
||||
self.fd_shared[i] = 0
|
||||
for i in range(PROC_MAX_DIRS):
|
||||
if self.dir_used[i]:
|
||||
dp: rawptr = self.dir_table[i]
|
||||
@@ -125,6 +154,7 @@ class ProcessManager:
|
||||
p.name[i] = ch
|
||||
i += 1
|
||||
p.name[i] = 0
|
||||
string.strcpy(&p.cwd[0], "/")
|
||||
j: int
|
||||
for j in range(MAX_PROC_THREADS):
|
||||
p.threads[j] = -1
|
||||
@@ -132,9 +162,28 @@ class ProcessManager:
|
||||
p.fd_used[j] = 0
|
||||
p.fd_table[j] = None
|
||||
p.fd_kind[j] = 0
|
||||
p.fd_flags[j] = 0
|
||||
p.fd_shared[j] = 0
|
||||
for j in range(PROC_MAX_DIRS):
|
||||
p.dir_used[j] = 0
|
||||
p.dir_table[j] = None
|
||||
for j in range(MAX_VMAS):
|
||||
p.vma_base[j] = 0
|
||||
p.vma_size[j] = 0
|
||||
p.vma_prot[j] = 0
|
||||
p.vma_kind[j] = 0
|
||||
p.vma_flags[j] = 0
|
||||
p.brk_start = 0
|
||||
p.brk_end = 0
|
||||
p.entry_rsp = 0
|
||||
p.parent_pid = 0
|
||||
p.exit_code = 0
|
||||
p.sig_mask = 0
|
||||
p.sig_pending = 0
|
||||
for j in range(65):
|
||||
p.sig_handler[j] = 0
|
||||
p.sig_actflags[j] = 0
|
||||
p.sig_restorer[j] = 0
|
||||
__asm__(f"""mov {asmout(p.pml4_root, ASM_DESCR.OUTPUT_REG)}, cr3""",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
|
||||
@@ -171,7 +220,10 @@ class ProcessManager:
|
||||
@staticmethod
|
||||
def create_process(name: str, entry: rawptr) -> Process*:
|
||||
m: ProcessManager* = _proc_mgr_ptr
|
||||
if m.count >= MAX_PROCESSES: return None
|
||||
if m.count >= MAX_PROCESSES:
|
||||
# 启动时已有 1(内核)+7(app) 个槽;旧上限 8 导致 run/runb 永远失败。
|
||||
serial.puts("[process] create_process: pid table full\n")
|
||||
return None
|
||||
pid: int = m.count
|
||||
m.count += 1
|
||||
p: Process* = &m.processes[pid]
|
||||
@@ -189,17 +241,38 @@ class ProcessManager:
|
||||
p.name[i] = ch
|
||||
i += 1
|
||||
p.name[i] = 0
|
||||
string.strcpy(&p.cwd[0], "/")
|
||||
for j in range(MAX_PROC_THREADS):
|
||||
p.threads[j] = -1
|
||||
for j in range(PROC_MAX_FDS):
|
||||
p.fd_used[j] = 0
|
||||
p.fd_table[j] = None
|
||||
p.fd_kind[j] = 0
|
||||
p.fd_flags[j] = 0
|
||||
p.fd_shared[j] = 0
|
||||
for j in range(PROC_MAX_DIRS):
|
||||
p.dir_used[j] = 0
|
||||
p.dir_table[j] = None
|
||||
for j in range(MAX_VMAS):
|
||||
p.vma_base[j] = 0
|
||||
p.vma_size[j] = 0
|
||||
p.vma_prot[j] = 0
|
||||
p.vma_kind[j] = 0
|
||||
p.vma_flags[j] = 0
|
||||
p.brk_start = 0
|
||||
p.brk_end = 0
|
||||
p.entry_rsp = 0
|
||||
p.parent_pid = 0
|
||||
p.exit_code = 0
|
||||
p.sig_mask = 0
|
||||
p.sig_pending = 0
|
||||
for j in range(65):
|
||||
p.sig_handler[j] = 0
|
||||
p.sig_actflags[j] = 0
|
||||
p.sig_restorer[j] = 0
|
||||
ks: rawptr = mm.malloc(KERN_STACK_SIZE)
|
||||
if ks is None:
|
||||
serial.puts("[process] kernel stack alloc failed\n")
|
||||
m.count = m.count - 1
|
||||
return None
|
||||
ks_size: uint64 = KERN_STACK_SIZE
|
||||
@@ -302,6 +375,60 @@ def drop_to_user_mode(entry: rawptr, user_rsp: uint64, kern_rsp: uint64, arg: ra
|
||||
ASM_DESCR.CLOBBER_R9, ASM_DESCR.CLOBBER_R10,
|
||||
ASM_DESCR.CLOBBER_RBP])
|
||||
|
||||
# Resume context used by fork/clone children: they start user mode at the
|
||||
# parent's post-syscall RIP with a full register set and RAX=0.
|
||||
export _resume_ctx: [20]uint64
|
||||
|
||||
def set_resume(rip: uint64, rsp: uint64, flags: uint64, rax: uint64, regs: uint64*):
|
||||
_resume_ctx[0] = rip
|
||||
_resume_ctx[1] = rsp
|
||||
_resume_ctx[2] = flags
|
||||
_resume_ctx[3] = rax
|
||||
i: int = 0
|
||||
while i < 12:
|
||||
_resume_ctx[4 + i] = regs[i]
|
||||
i += 1
|
||||
|
||||
def drop_to_user_mode_ctx(kern_rsp: uint64):
|
||||
__asm__(f"""cli
|
||||
mov rsp, {asminp(kern_rsp, ASM_DESCR.REG_ANY)}
|
||||
mov ecx, 0xC0000101
|
||||
rdmsr
|
||||
shl rdx, 32
|
||||
or rax, rdx
|
||||
test rax, rax
|
||||
jz 1f
|
||||
swapgs
|
||||
1:
|
||||
lea rbx, [rip + _resume_ctx]
|
||||
mov r15, [rbx+32]
|
||||
mov r14, [rbx+40]
|
||||
mov r13, [rbx+48]
|
||||
mov r12, [rbx+56]
|
||||
mov r10, [rbx+64]
|
||||
mov r9, [rbx+72]
|
||||
mov r8, [rbx+80]
|
||||
mov rbp, [rbx+88]
|
||||
mov rdi, [rbx+96]
|
||||
mov rsi, [rbx+104]
|
||||
mov rdx, [rbx+112]
|
||||
mov rcx, [rbx+0]
|
||||
mov r11, [rbx+16]
|
||||
mov rax, [rbx+24]
|
||||
push 0x23
|
||||
push qword ptr [rbx+8]
|
||||
push r11
|
||||
push 0x2B
|
||||
push rcx
|
||||
mov rbx, [rbx+120]
|
||||
iretq""",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX,
|
||||
ASM_DESCR.CLOBBER_RBX, ASM_DESCR.CLOBBER_RCX,
|
||||
ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI,
|
||||
ASM_DESCR.CLOBBER_RDI, ASM_DESCR.CLOBBER_R8,
|
||||
ASM_DESCR.CLOBBER_R9, ASM_DESCR.CLOBBER_R10,
|
||||
ASM_DESCR.CLOBBER_RBP])
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 用户态线程(THREAD_CREATE)上下文
|
||||
|
||||
@@ -10,7 +10,7 @@ define THREAD_READY = 0
|
||||
define THREAD_RUNNING = 1
|
||||
define THREAD_BLOCKED = 2
|
||||
define THREAD_DONE = 3
|
||||
define MAX_THREADS = 16
|
||||
define MAX_THREADS = 64
|
||||
define THREAD_STACK_SIZE = 65536
|
||||
define SCHED_QUANTUM = 10
|
||||
|
||||
@@ -21,6 +21,34 @@ export _isr_scratch_tbl: [MAX_THREADS * 6]uint64
|
||||
export _switch_old_tid: int = 0
|
||||
export _switch_new_tid: int = 0
|
||||
|
||||
# Per-thread IA32_FS_BASE (glibc TLS). Kept outside Thread: Thread has a
|
||||
# hard-coded 600-byte layout checked by _thread_entry.
|
||||
define IA32_FS_BASE = 0xC0000100
|
||||
_fs_base_tbl: [MAX_THREADS]uint64
|
||||
_syscall_frame_tbl: [MAX_THREADS]uint64
|
||||
_clear_child_tid: [MAX_THREADS]uint64
|
||||
|
||||
def _msr_read(msr: uint32) -> uint64:
|
||||
lo: uint32
|
||||
hi: uint32
|
||||
__asm__(f"""mov ecx, {asminp(msr, ASM_DESCR.REG_ANY)}
|
||||
rdmsr
|
||||
mov {asmout(lo, ASM_DESCR.OUTPUT_REG)}, eax
|
||||
mov {asmout(hi, ASM_DESCR.OUTPUT_REG)}, edx""",
|
||||
out=[asmout(lo, ASM_DESCR.OUTPUT_REG), asmout(hi, ASM_DESCR.OUTPUT_REG)],
|
||||
op=[ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RDX])
|
||||
return uint64(lo) | (uint64(hi) << 32)
|
||||
|
||||
def _msr_write(msr: uint32, val: uint64):
|
||||
lo: uint32 = uint32(val & uint64(0xFFFFFFFF))
|
||||
hi: uint32 = uint32(val >> 32)
|
||||
__asm__(f"""mov ecx, {asminp(msr, ASM_DESCR.REG_ANY)}
|
||||
mov eax, {asminp(lo, ASM_DESCR.REG_ANY)}
|
||||
mov edx, {asminp(hi, ASM_DESCR.REG_ANY)}
|
||||
wrmsr""",
|
||||
op=[ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RDX])
|
||||
|
||||
|
||||
|
||||
class Thread:
|
||||
rsp: uint64
|
||||
@@ -103,12 +131,12 @@ class Scheduler:
|
||||
__asm__(f"""mov [{asminp(main_rsp_ptr, ASM_DESCR.REG_ANY)}], rsp""",
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY])
|
||||
# fail-fast:_thread_entry 的 asm 以硬编码偏移访问线程表(sizeof(Thread)=600、
|
||||
# current_tid=+9600=16*600)。增删 Thread 字段会让它们失配 ⇒ 线程入口会按错误的
|
||||
# current_tid=+38400=64*600)。增删 Thread 字段会让它们失配 ⇒ 线程入口会按错误的
|
||||
# 偏移取 func/arg 并 call 任意地址。这里直接核对,不符立即停机暴露。
|
||||
thr0_addr: uint64 = uint64(&s.threads[0])
|
||||
curt_addr: uint64 = uint64(&s.current_tid)
|
||||
if Thread.__sizeof__() != 600 or (curt_addr - thr0_addr) != 9600:
|
||||
serial.puts("[sched] FATAL: Thread 布局与 _thread_entry 硬编码偏移(600/9600)不一致\n")
|
||||
if Thread.__sizeof__() != 600 or (curt_addr - thr0_addr) != 38400:
|
||||
serial.puts("[sched] FATAL: Thread 布局与 _thread_entry 硬编码偏移(600/38400)不一致\n")
|
||||
while True:
|
||||
asm.sti()
|
||||
asm.hlt()
|
||||
@@ -117,7 +145,8 @@ class Scheduler:
|
||||
def _yield():
|
||||
global _switch_old_rsp, _switch_new_rsp, _yield_cnt, _yield_lock
|
||||
global _switch_old_tid, _switch_new_tid
|
||||
global _switch_old_fpu, _switch_new_fpu
|
||||
global _switch_old_fpu, _switch_new_fpu
|
||||
global _fs_base_tbl
|
||||
_if_saved: int = 0
|
||||
__asm__(f"""pushfq
|
||||
pop rax
|
||||
@@ -176,6 +205,9 @@ class Scheduler:
|
||||
cpu.set_kernel_rsp0(next_ks)
|
||||
_switch_old_tid = old_tid
|
||||
_switch_new_tid = next_tid
|
||||
# Per-thread TLS: save the outgoing FS base, load the incoming one.
|
||||
_fs_base_tbl[old_tid] = _msr_read(uint32(IA32_FS_BASE))
|
||||
_msr_write(uint32(IA32_FS_BASE), _fs_base_tbl[next_tid])
|
||||
_yield_lock = 0
|
||||
_do_switch()
|
||||
if _if_saved:
|
||||
@@ -306,8 +338,14 @@ class Scheduler:
|
||||
现场 [0E] rip=0x0 err=0x15(用户态取指缺页)cr2=0x0 rsp=0x7fffffde0000,tid 为该 app 线程,
|
||||
紧跟在 [syscall] exit called 之后。
|
||||
"""
|
||||
global _clear_child_tid
|
||||
s: Scheduler* = _sched_ptr
|
||||
tid: int = s.current_tid
|
||||
ct: uint64 = _clear_child_tid[tid]
|
||||
if ct != 0:
|
||||
cw: uint32* = uint32*(ct)
|
||||
cw[0] = uint32(0)
|
||||
_clear_child_tid[tid] = 0
|
||||
if tid > 0 and tid < MAX_THREADS:
|
||||
s.threads[tid].state = THREAD_DONE
|
||||
proc.free_user_thread(proc.find_user_slot_by_tid(tid))
|
||||
@@ -318,6 +356,33 @@ class Scheduler:
|
||||
asm.hlt()
|
||||
Scheduler._yield()
|
||||
|
||||
@staticmethod
|
||||
def exit_process(pid: int):
|
||||
"""Linux exit_group: retire every thread of `pid`, release the process, never return."""
|
||||
global _clear_child_tid
|
||||
s: Scheduler* = _sched_ptr
|
||||
i: int = 0
|
||||
while i < s.thread_count:
|
||||
if s.threads[i].pid == pid:
|
||||
ct: uint64 = _clear_child_tid[i]
|
||||
if ct != 0:
|
||||
cw: uint32* = uint32*(ct)
|
||||
cw[0] = uint32(0)
|
||||
_clear_child_tid[i] = 0
|
||||
s.threads[i].state = THREAD_DONE
|
||||
slot: int = proc.find_user_slot_by_tid(i)
|
||||
if slot >= 0:
|
||||
proc.free_user_thread(slot)
|
||||
i += 1
|
||||
p: proc.Process* = proc.ProcessManager.get_process(pid)
|
||||
if p is not None:
|
||||
p.exit()
|
||||
Scheduler._yield()
|
||||
while True:
|
||||
asm.sti()
|
||||
asm.hlt()
|
||||
Scheduler._yield()
|
||||
|
||||
|
||||
_sched_storage: Scheduler = Scheduler()
|
||||
export _sched_ptr: Scheduler* = &_sched_storage
|
||||
@@ -326,6 +391,34 @@ export _switch_new_rsp: rawptr
|
||||
export _switch_old_fpu: rawptr
|
||||
export _switch_new_fpu: rawptr
|
||||
|
||||
def set_fs_base(base: uint64):
|
||||
global _fs_base_tbl
|
||||
s: Scheduler* = _sched_ptr
|
||||
_fs_base_tbl[s.current_tid] = base
|
||||
_msr_write(uint32(IA32_FS_BASE), base)
|
||||
|
||||
def set_thread_fs_base(tid: int, base: uint64):
|
||||
global _fs_base_tbl
|
||||
if tid >= 0 and tid < MAX_THREADS:
|
||||
_fs_base_tbl[tid] = base
|
||||
|
||||
def get_fs_base() -> uint64:
|
||||
return _msr_read(uint32(IA32_FS_BASE))
|
||||
|
||||
def set_syscall_frame(f: uint64):
|
||||
global _syscall_frame_tbl
|
||||
s: Scheduler* = _sched_ptr
|
||||
_syscall_frame_tbl[s.current_tid] = f
|
||||
|
||||
def get_syscall_frame() -> uint64:
|
||||
s: Scheduler* = _sched_ptr
|
||||
return _syscall_frame_tbl[s.current_tid]
|
||||
|
||||
def set_clear_child_tid(tid: int, addr: uint64):
|
||||
global _clear_child_tid
|
||||
if tid >= 0 and tid < MAX_THREADS:
|
||||
_clear_child_tid[tid] = addr
|
||||
|
||||
@__attribute__(naked)
|
||||
def _do_switch():
|
||||
__asm__(f"""push rax
|
||||
@@ -394,20 +487,20 @@ def _do_switch():
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX])
|
||||
|
||||
# Thread 布局:rsp0/stack8/stack_size16/func24/arg32/state40/tid44/pid48/kern_stack56/fpu_area64/fpu_ptr592
|
||||
# ⇒ sizeof(Thread) = 600,Scheduler.threads[16] 占 9600 字节 ⇒ current_tid 在 +9600。
|
||||
# 增删 Thread 字段时必须同步下面 asm 里的 9600 / 600(以及 +24/+32/+40 三个字段偏移)。
|
||||
# ⇒ sizeof(Thread) = 600,Scheduler.threads[64] 占 38400 字节 ⇒ current_tid 在 +38400。
|
||||
# 增删 Thread 字段时必须同步下面 asm 里的 38400 / 600(以及 +24/+32/+40 三个字段偏移)。
|
||||
@__attribute__(naked)
|
||||
def _thread_entry():
|
||||
__asm__(f"""sub rsp, 8
|
||||
mov rax, [rip + _sched_ptr]
|
||||
mov ecx, dword ptr [rax + 9600]
|
||||
mov ecx, dword ptr [rax + 38400]
|
||||
movsxd rcx, ecx
|
||||
imul rcx, rcx, 600
|
||||
mov rdi, [rax + rcx + 32]
|
||||
mov rax, [rax + rcx + 24]
|
||||
call rax
|
||||
mov rax, [rip + _sched_ptr]
|
||||
mov ecx, dword ptr [rax + 9600]
|
||||
mov ecx, dword ptr [rax + 38400]
|
||||
movsxd rcx, ecx
|
||||
imul rcx, rcx, 600
|
||||
mov dword ptr [rax + rcx + 40], 3
|
||||
|
||||
+3
-1
@@ -23,7 +23,9 @@
|
||||
"output": "kernel.elf"
|
||||
},
|
||||
"includes": [
|
||||
"../../../includes"
|
||||
"../..",
|
||||
"../../../includes",
|
||||
"../"
|
||||
],
|
||||
"target": {
|
||||
"triple": "x86_64-none-elf",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Package marker for the shared Linux ABI module.
|
||||
define ABI_VERSION = 1
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
# Linux x86-64 ABI constants and structures shared by the kernel and vpsdk.
|
||||
#
|
||||
# The byte layouts must match Linux exactly: glibc reads these structs
|
||||
# directly and never goes through any ViperOS wrapper.
|
||||
#
|
||||
# This module is imported by both sides:
|
||||
# VKernel/Kernel/intr/syscall.vp (kernel project gains "../.." include)
|
||||
# vpsdk/syscall.vp / vpsdk/fs.vp (apps already include the repo root)
|
||||
|
||||
# ---------------------------------------------------------------- errno ----
|
||||
define EPERM = 1
|
||||
define ENOENT = 2
|
||||
define ESRCH = 3
|
||||
define EINTR = 4
|
||||
define EIO = 5
|
||||
define ENXIO = 6
|
||||
define E2BIG = 7
|
||||
define ENOEXEC = 8
|
||||
define EBADF = 9
|
||||
define ECHILD = 10
|
||||
define EAGAIN = 11
|
||||
define ENOMEM = 12
|
||||
define EACCES = 13
|
||||
define EFAULT = 14
|
||||
define ENOTBLK = 15
|
||||
define EBUSY = 16
|
||||
define EEXIST = 17
|
||||
define EXDEV = 18
|
||||
define ENODEV = 19
|
||||
define ENOTDIR = 20
|
||||
define EISDIR = 21
|
||||
define EINVAL = 22
|
||||
define ENFILE = 23
|
||||
define EMFILE = 24
|
||||
define ENOTTY = 25
|
||||
define ETXTBSY = 26
|
||||
define EFBIG = 27
|
||||
define ENOSPC = 28
|
||||
define ESPIPE = 29
|
||||
define EROFS = 30
|
||||
define EMLINK = 31
|
||||
define EPIPE = 32
|
||||
define EDOM = 33
|
||||
define ERANGE = 34
|
||||
define EDEADLK = 35
|
||||
define ENAMETOOLONG = 36
|
||||
define ENOLCK = 37
|
||||
define ENOSYS = 38
|
||||
define ENOTEMPTY = 39
|
||||
define ELOOP = 40
|
||||
define EOVERFLOW = 75
|
||||
define ENOTSUP = 95
|
||||
define EOPNOTSUPP = 95
|
||||
define ETIMEDOUT = 110
|
||||
|
||||
# ------------------------------------------------------------ open flags ----
|
||||
# Values are the Linux x86-64 ones (octal in the man page).
|
||||
define O_RDONLY = 0
|
||||
define O_WRONLY = 1
|
||||
define O_RDWR = 2
|
||||
define O_ACCMODE = 3
|
||||
define O_CREAT = 64
|
||||
define O_EXCL = 128
|
||||
define O_NOCTTY = 256
|
||||
define O_TRUNC = 512
|
||||
define O_APPEND = 1024
|
||||
define O_NONBLOCK = 2048
|
||||
define O_DSYNC = 4096
|
||||
define O_DIRECT = 16384
|
||||
define O_DIRECTORY = 65536
|
||||
define O_NOFOLLOW = 131072
|
||||
define O_CLOEXEC = 524288
|
||||
|
||||
# --------------------------------------------------------------- fcntl ----
|
||||
define F_DUPFD = 0
|
||||
define F_GETFD = 1
|
||||
define F_SETFD = 2
|
||||
define F_GETFL = 3
|
||||
define F_SETFL = 4
|
||||
define F_DUPFD_CLOEXEC = 1030
|
||||
define FD_CLOEXEC = 1
|
||||
|
||||
# ----------------------------------------------------------- at / dirent ----
|
||||
define AT_FDCWD = -100
|
||||
define AT_SYMLINK_NOFOLLOW = 256
|
||||
define AT_REMOVEDIR = 512
|
||||
define AT_EACCESS = 512
|
||||
define AT_EMPTY_PATH = 4096
|
||||
|
||||
define DT_UNKNOWN = 0
|
||||
define DT_FIFO = 1
|
||||
define DT_CHR = 2
|
||||
define DT_DIR = 4
|
||||
define DT_BLK = 6
|
||||
define DT_REG = 8
|
||||
define DT_LNK = 10
|
||||
define DT_SOCK = 12
|
||||
|
||||
# ------------------------------------------------------------ mmap/mprot ----
|
||||
define PROT_NONE = 0
|
||||
define PROT_READ = 1
|
||||
define PROT_WRITE = 2
|
||||
define PROT_EXEC = 4
|
||||
define MAP_SHARED = 1
|
||||
define MAP_PRIVATE = 2
|
||||
define MAP_FIXED = 16
|
||||
define MAP_ANONYMOUS = 32
|
||||
define MAP_GROWSDOWN = 256
|
||||
define MAP_NORESERVE = 16384
|
||||
define MAP_POPULATE = 32768
|
||||
define MAP_FAILED: uint64 = 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
# ---------------------------------------------------------------- mremap ----
|
||||
define MREMAP_MAYMOVE = 1
|
||||
define MREMAP_FIXED = 2
|
||||
|
||||
# ------------------------------------------------------------- arch_prctl ----
|
||||
define ARCH_SET_GS = 0x1001
|
||||
define ARCH_SET_FS = 0x1002
|
||||
define ARCH_GET_FS = 0x1003
|
||||
define ARCH_GET_GS = 0x1004
|
||||
|
||||
# ---------------------------------------------------------------- rlimit ----
|
||||
define RLIMIT_CPU = 0
|
||||
define RLIMIT_FSIZE = 1
|
||||
define RLIMIT_DATA = 2
|
||||
define RLIMIT_STACK = 3
|
||||
define RLIMIT_CORE = 4
|
||||
define RLIMIT_NOFILE = 7
|
||||
define RLIM_INFINITY: uint64 = 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
# ------------------------------------------------------------- file modes ----
|
||||
define S_IFMT = 61440
|
||||
define S_IFDIR = 16384
|
||||
define S_IFCHR = 8192
|
||||
define S_IFIFO = 4096
|
||||
define S_IFREG = 32768
|
||||
define MODE_FILE = 33188 # 0100644
|
||||
define MODE_DIR = 16877 # 040755
|
||||
define MODE_CHAR = 8630 # 020666
|
||||
define MODE_FIFO = 4534 # 010666
|
||||
|
||||
define SEEK_SET = 0
|
||||
define SEEK_CUR = 1
|
||||
define SEEK_END = 2
|
||||
|
||||
define PATH_MAX = 256
|
||||
define SIZEOF_LINUX_STAT = 144
|
||||
define SIZEOF_UTSNAME = 390
|
||||
define SIZEOF_DIRENT64 = 19
|
||||
|
||||
# ---------------------------------------------------------------- structs ----
|
||||
# struct stat, x86-64 Linux layout (144 bytes).
|
||||
@__attribute__(packed)
|
||||
class linux_stat:
|
||||
st_dev: uint64
|
||||
st_ino: uint64
|
||||
st_nlink: uint64
|
||||
st_mode: uint32
|
||||
st_uid: uint32
|
||||
st_gid: uint32
|
||||
pad0: uint32
|
||||
st_rdev: uint64
|
||||
st_size: uint64
|
||||
st_blksize: uint64
|
||||
st_blocks: uint64
|
||||
st_atime_sec: uint64
|
||||
st_atime_nsec: uint64
|
||||
st_mtime_sec: uint64
|
||||
st_mtime_nsec: uint64
|
||||
st_ctime_sec: uint64
|
||||
st_ctime_nsec: uint64
|
||||
res0: uint64
|
||||
res1: uint64
|
||||
res2: uint64
|
||||
|
||||
# struct linux_dirent64 header; the NUL-terminated name follows immediately.
|
||||
@__attribute__(packed)
|
||||
class linux_dirent64:
|
||||
d_ino: uint64
|
||||
d_off: uint64
|
||||
d_reclen: uint16
|
||||
d_type: uint8
|
||||
|
||||
@__attribute__(packed)
|
||||
class linux_timespec:
|
||||
tv_sec: uint64
|
||||
tv_nsec: uint64
|
||||
|
||||
@__attribute__(packed)
|
||||
class linux_utsname:
|
||||
sysname: [65]char
|
||||
nodename: [65]char
|
||||
release: [65]char
|
||||
version: [65]char
|
||||
machine: [65]char
|
||||
domainname: [65]char
|
||||
|
||||
@__attribute__(packed)
|
||||
class linux_rlimit:
|
||||
cur: uint64
|
||||
max: uint64
|
||||
|
||||
class iovec:
|
||||
base: rawptr
|
||||
len: uint64
|
||||
@@ -7,7 +7,7 @@ import subprocess
|
||||
# ============================================================================
|
||||
# viperc 编译器路径 —— 唯一来源,改这里即可
|
||||
# ============================================================================
|
||||
VIPERC = r'..\..\ViperC\__vpcbuild__\output\viperc.exe'
|
||||
VIPERC = r'..\..\ViperC\__vpcbuild6__\output\viperc.exe'
|
||||
|
||||
|
||||
# 解析参数
|
||||
@@ -17,6 +17,18 @@ noapp = '--noapp' in sys.argv
|
||||
bootloader = '--bootloader' in sys.argv
|
||||
# --onlyrun:跳过全部编译/打包,直接用现有 disk.img 启动 QEMU
|
||||
only_run = '--onlyrun' in sys.argv
|
||||
# --headless:QEMU 不开窗口(-display none),串口仍写入 serial.log。
|
||||
# 自动化调试/无人值守验证时使用;需要看画面时不要带这个参数。
|
||||
headless = '--headless' in sys.argv
|
||||
# --runsecs=N:启动 N 秒后自动结束 QEMU(0=一直运行到客户机退出)。
|
||||
# 与 --headless 搭配可用于无人值守的冒烟测试。
|
||||
run_secs = 0
|
||||
for _arg in sys.argv:
|
||||
if _arg.startswith('--runsecs='):
|
||||
try:
|
||||
run_secs = int(_arg[len('--runsecs='):])
|
||||
except ValueError:
|
||||
run_secs = 0
|
||||
# --onlyapp:只编译库与应用(保留兼容;现在这已是默认行为)
|
||||
only_app = '--onlyapp' in sys.argv
|
||||
# --kernel:显式重编内核。不带 --app 时**默认就编内核**(见下方 app_filter 解析后的修正);
|
||||
@@ -45,6 +57,17 @@ if kernel_only and nokernel:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def SyncSyscallTable():
|
||||
"""同步 syscall 表:Scripts/syscall_table.py 是唯一权威来源。
|
||||
|
||||
VKernel 与 vpsdk 各自持有一份 enum(vpsdk 不能依赖内核),构建前统一
|
||||
重新生成,避免两份表手工漂移。生成器失败时保留现有文件继续构建。
|
||||
"""
|
||||
rc = os.system(f'"{sys.executable}" Scripts/syscall_table.py --write')
|
||||
if rc != 0:
|
||||
print("[build] 警告: syscall 表同步失败,继续使用现有文件")
|
||||
|
||||
|
||||
def BuildKernel():
|
||||
"""编译内核:链接器一次产出带符号 kernel.elf,再由 objcopy 二次处理成裸镜像 kernel.bin"""
|
||||
cmd = rf'{VIPERC} --project ./VKernel/project.vpj --clear-cache --rebuild-includes'
|
||||
@@ -74,6 +97,7 @@ APP_LIST = [
|
||||
("scene3d", "Apps/Scene3D", "scene3d.elf", "scene3d.elf"),
|
||||
("explorer", "Apps/Explorer", "explorer.elf", "explorer.elf"),
|
||||
("fontdemo", "Apps/FontDemo", "fontdemo.elf", "fontdemo.elf"),
|
||||
("imagesee", "Apps/ImageSee", "imagesee.elf", "imagesee.elf"),
|
||||
]
|
||||
# 动态库表:(工程目录, 产物文件名)
|
||||
LIB_LIST = [
|
||||
@@ -97,6 +121,26 @@ def CopyFonts():
|
||||
shutil.copy(src, "./disk/fonts/font.ttf")
|
||||
|
||||
|
||||
def CopyAssets():
|
||||
"""Copy image demo assets (Assets/images) to /IMAGES on the disk."""
|
||||
src_dir = "Assets/images"
|
||||
if not os.path.isdir(src_dir):
|
||||
print("[build] 警告: 缺少 Assets/images,ImageSee 将没有演示图片")
|
||||
return
|
||||
os.makedirs("./disk/images", exist_ok=True)
|
||||
for name in os.listdir(src_dir):
|
||||
low = name.lower()
|
||||
if low.endswith(".png") or low.endswith(".jpg") or low.endswith(".jpeg"):
|
||||
shutil.copy(os.path.join(src_dir, name), os.path.join("./disk/images", name))
|
||||
print(f"[build] asset {name} -> disk/images/{name}")
|
||||
# Desktop wallpaper used by Explorer (separate from the ImageSee gallery).
|
||||
bg = "Assets/background.jpg"
|
||||
if os.path.exists(bg):
|
||||
os.makedirs("./disk/sys/images/screen", exist_ok=True)
|
||||
shutil.copy(bg, "./disk/sys/images/screen/background.jpg")
|
||||
print("[build] asset background.jpg -> disk/sys/images/screen/background.jpg")
|
||||
|
||||
|
||||
def BuildLibs():
|
||||
"""编译 Libs/ 下的动态库(应用运行期经 LOAD_SO 加载)。"""
|
||||
os.makedirs("./disk/libs", exist_ok=True)
|
||||
@@ -222,11 +266,25 @@ def RunQemu():
|
||||
'-serial', f'file:{serial_log}',
|
||||
# '-no-reboot'
|
||||
]
|
||||
if headless:
|
||||
qemu_cmd += ['-display', 'none']
|
||||
|
||||
print(f"[build] QEMU cmd: {' '.join(qemu_cmd)}")
|
||||
|
||||
proc = subprocess.Popen(qemu_cmd)
|
||||
proc.wait()
|
||||
if run_secs > 0:
|
||||
try:
|
||||
proc.wait(timeout=run_secs)
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f"[build] --runsecs={run_secs}: 自动结束 QEMU")
|
||||
proc.terminate()
|
||||
try:
|
||||
proc.wait(timeout=10)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
proc.wait()
|
||||
else:
|
||||
proc.wait()
|
||||
|
||||
print(f"[build] serial_log path: {serial_log}")
|
||||
print(f"[build] file exists: {os.path.exists(serial_log)}")
|
||||
@@ -242,6 +300,7 @@ def RunQemu():
|
||||
if only_run:
|
||||
print("[build] --onlyrun: 跳过编译/打包,直接用现有 disk.img 启动 QEMU")
|
||||
else:
|
||||
SyncSyscallTable()
|
||||
if kernel_only:
|
||||
print("[build] --kernel-only: 只编译内核")
|
||||
BuildKernel()
|
||||
@@ -251,8 +310,10 @@ else:
|
||||
else:
|
||||
print("[build] 跳过内核编译(--app 或 --nokernel)")
|
||||
BuildApps()
|
||||
CopyAssets()
|
||||
BuildBootloader()
|
||||
CopyFonts()
|
||||
BuildDisk()
|
||||
|
||||
RunQemu()
|
||||
if "--norun" not in sys.argv:
|
||||
RunQemu()
|
||||
@@ -0,0 +1,114 @@
|
||||
# ImageSee 图像解码库
|
||||
|
||||
`vpsdk/imagesee` 是一个纯用户态的 PNG / JPEG 解码库,配套示例应用
|
||||
`Apps/ImageSee` 在窗口中并排显示解码结果。
|
||||
|
||||
## 1. 目录与模块
|
||||
|
||||
```text
|
||||
vpsdk/imagesee/
|
||||
├── imagesee.vp 公共 API:load / decode_memory / checksum
|
||||
├── image.vp Image 类型与像素打包(0xAARRGGBB)
|
||||
├── inflate.vp zlib / DEFLATE(stored / fixed / dynamic Huffman)
|
||||
├── png.vp PNG:全部颜色类型、1/2/4/8/16 位、Adam7
|
||||
└── jpeg.vp JPEG:baseline/extended sequential、4:4:4/4:2:2/4:2:0
|
||||
|
||||
Apps/ImageSee/ 示例应用(窗口 + 两块 FBCanvas)
|
||||
Assets/images/ 示例图片(由 Scripts/gen_imagesee_assets.py 生成)
|
||||
```
|
||||
|
||||
## 2. 公共 API
|
||||
|
||||
```python
|
||||
import vpsdk.imagesee.imagesee as imagesee
|
||||
|
||||
img = imagesee.load("/IMAGES/SAMPLE.PNG") # 失败返回 None
|
||||
if img is not None:
|
||||
w = img.width
|
||||
h = img.height
|
||||
c = img.pixels[y * w + x] # 0xAARRGGBB
|
||||
r = (c >> 16) & 0xFF
|
||||
g = (c >> 8) & 0xFF
|
||||
b = c & 0xFF
|
||||
|
||||
img2 = imagesee.decode_memory(buf, size) # 已有字节流时使用
|
||||
sum32 = imagesee.checksum(img) # FNV-1a,用于回归比对
|
||||
name = imagesee.format_name(img) # "png" / "jpeg"
|
||||
```
|
||||
|
||||
像素格式与 ViperOS 窗口 / `FBCanvas` 一致:`0xAARRGGBB`,alpha 字节目前由
|
||||
合成层忽略,解码器一律输出 255(PNG 的透明像素保留 alpha=0 于像素字中)。
|
||||
|
||||
## 3. PNG 支持
|
||||
|
||||
- 签名、IHDR、PLTE、tRNS、IDAT(可多块)、IEND
|
||||
- 颜色类型:0(灰度)、2(RGB)、3(调色板)、4(灰度+alpha)、6(RGBA)
|
||||
- 位深:1/2/4/8/16(按 PNG 规则缩小到 8 位输出)
|
||||
- 过滤:None / Sub / Up / Average / Paeth
|
||||
- Adam7 隔行扫描
|
||||
- 解码后输出 RGBA;调色板 tRNS、灰度和 RGB 的透明色都会转成 alpha=0
|
||||
|
||||
## 4. JPEG 支持
|
||||
|
||||
- Baseline / extended sequential Huffman(SOF0 / SOF1)
|
||||
- 灰度与 YCbCr,色度子采样 4:4:4 / 4:2:2 / 4:2:0(以及任意 H/V 因子)
|
||||
- DQT(8/16 位)、DHT、DRI + RSTn 重启标记
|
||||
- 组件按 ID 识别(1=Y、2=Cb、3=Cr),也兼容 Adobe APP14 的 RGB 变换
|
||||
- 8×8 浮点 IDCT,YCbCr → RGB 后输出
|
||||
|
||||
**限制**:渐进式 JPEG(SOF2)目前明确返回 `None`(不做“解一半”的错误输出);
|
||||
CMYK/YCCK 返回 `None`;不支持 EXIF 方向、缩略图、算术编码。
|
||||
|
||||
## 5. 示例应用
|
||||
|
||||
`/APPS/IMAGESEE.ELF` 启动后:
|
||||
|
||||
1. 依次解码并打印 `/IMAGES/SAMPLE.PNG`、`PHOTO.JPG`、`GRAY.JPG`、
|
||||
`PAL.PNG`、`INTER.PNG` 的尺寸、FNV 校验和与 3 个采样像素;
|
||||
2. 创建窗口,左画 PNG、右画 JPEG(等比缩放居中);
|
||||
3. 失败时对应面板显示品红/深蓝棋盘,串口打印 `FAILED stage=N`。
|
||||
|
||||
`build.py` 会:
|
||||
|
||||
- 编译 `Apps/ImageSee` 到 `disk/apps/imagesee.elf`
|
||||
- 把 `Assets/images/*.png|jpg` 复制到 `disk/images/`
|
||||
- 内核 `main.vp` 在 Explorer/FontDemo 之后自动 spawn ImageSee(pid=7)
|
||||
|
||||
重建示例图片:
|
||||
|
||||
```powershell
|
||||
python Scripts/gen_imagesee_assets.py # 需要 Pillow,仅生成资源时使用
|
||||
```
|
||||
|
||||
## 6. 已验证
|
||||
|
||||
2026-10-28,QEMU 512MB,无头启动:
|
||||
|
||||
| 文件 | 类型 | 结果 |
|
||||
|---|---|---|
|
||||
| sample.png | RGBA,非隔行 | 解码像素/校验和与 PIL 完全一致 |
|
||||
| pal.png | 调色板 + tRNS | 完全一致 |
|
||||
| inter.png | RGBA,Adam7,alpha 渐变 | 完全一致 |
|
||||
| photo.jpg | baseline 4:2:0 | 尺寸/采样点与 PIL 在 ±11 内(IDCT/上采样差异) |
|
||||
| gray.jpg | 灰度 baseline | 采样点完全一致 |
|
||||
|
||||
## 7. Explorer 桌面壁纸
|
||||
|
||||
Explorer 启动时调用 `imagesee.load("/sys/images/screen/background.jpg")`:
|
||||
|
||||
- 成功:按“cover”等比裁切铺满 framebuffer,任务栏/窗口叠加在壁纸上
|
||||
- 失败(文件不存在/解码失败):重试一次;仍失败则保持原来的深色纯色背景
|
||||
- 构建:`build.py` 把 `Assets/background.jpg` 复制到
|
||||
`disk/sys/images/screen/background.jpg`
|
||||
- 重新生成壁纸:`python Scripts/gen_imagesee_assets.py`(960×540 baseline JPEG)
|
||||
|
||||
## 8. 内存
|
||||
|
||||
解码使用 `stdlib.malloc`(mmap 后端,`free` 为空操作)。一次 `load` 会保留:
|
||||
|
||||
- 压缩文件字节
|
||||
- DEFLATE 解压后的扫描线缓冲(PNG)
|
||||
- 最终 `w*h*4` 像素
|
||||
- JPEG 的量化系数与分量平面
|
||||
|
||||
因此适合中小图片(示例 320×220)。超大图片(>4096²)不建议在当前堆上尝试。
|
||||
+382
@@ -0,0 +1,382 @@
|
||||
# ViperOS syscall 与 Linux x86-64 ABI 对齐计划
|
||||
|
||||
状态:**P0–P6 完成**(含明确受限项;内核/应用构建通过,`fork`、`wait4`、信号投递已在 QEMU 冒烟验证)
|
||||
最后更新:2026-10-28
|
||||
|
||||
目标:让 ViperOS 的 syscall 面逐步收敛到 Linux x86-64 ABI,最终能够**直接运行静态/动态链接的 glibc 程序**。
|
||||
|
||||
非目标(至少本计划内不做):
|
||||
|
||||
- 网络协议栈相关 syscall(socket/connect/…)
|
||||
- ptrace / seccomp / cgroup / namespace
|
||||
- 完整 VFS(符号链接、硬链接、inode 语义)
|
||||
- 与 Linux 内核模块、io_uring、epoll 的兼容
|
||||
|
||||
---
|
||||
|
||||
## 1. 现状
|
||||
|
||||
### 1.1 入口与调用约定
|
||||
|
||||
| 项目 | 现状 |
|
||||
|---|---|
|
||||
| 指令 | `syscall`(MSR `STAR`/`LSTAR`/`FMASK`),另注册了 DPL=3 的 `int 0x80` 遗留门 |
|
||||
| 调用号 | `rax` |
|
||||
| 参数 | `rdi`, `rsi`, `rdx`, `r10`, `r8`, `r9`(与 Linux 一致) |
|
||||
| 返回 | `rax`;目前正负混用 |
|
||||
| 错误 | 无 `errno`、无 TLS;部分返回 `-1`,部分返回 FAT32 `FRESULT` **正数**,未知调用号返回 `0` |
|
||||
| 表 | `VKernel/Kernel/intr/syscall.vp` 与 `vpsdk/syscall.vp` 两份手工镜像 |
|
||||
|
||||
### 1.2 已实现的调用
|
||||
|
||||
权威列表见 `Scripts/syscall_table.py`(`build.py` 构建前自动同步内核与 SDK)。
|
||||
截至 P6,Linux 号段已覆盖:
|
||||
|
||||
```text
|
||||
文件 open/openat/close/read/write/pread/pwrite/lseek/stat/fstat/newfstatat/
|
||||
getdents64/dup/dup2/dup3/fcntl/mkdir/unlink/rename/rmdir/getcwd/chdir/
|
||||
access/faccessat/readlink/readlinkat
|
||||
内存 brk/mmap/munmap/mprotect/mremap/madvise
|
||||
进程 getpid/getppid/gettid/exit/exit_group/wait4/clone/fork/execve
|
||||
线程 arch_prctl(FS)/set_tid_address/set_robust_list/futex/sched_yield
|
||||
时间 clock_gettime/clock_getres/gettimeofday/nanosleep
|
||||
信号 rt_sigaction/rt_sigprocmask/rt_sigreturn/kill
|
||||
IPC pipe/pipe2/readv/writev/poll(超时桩)
|
||||
系统 uname/getuid/geteuid/getgid/getegid/prlimit64/getrandom/reboot
|
||||
```
|
||||
|
||||
私有段仅剩兼容别名(`LOAD_SO`/`SO_CALL1`/`SPAWN_PROC`/`THREAD_CREATE_OLD`/
|
||||
`REBOOT`/`SHUTDOWN`)。
|
||||
|
||||
### 1.3 已知偏差
|
||||
|
||||
- `execve(59)` 通过“加载新进程 + 退出调用者”模拟,PID 会改变
|
||||
- `fork(57)` / `clone(56)` 会复制/共享地址空间;`CLONE_VM` 无 `CLONE_THREAD`
|
||||
的 vfork 场景按同进程线程近似
|
||||
- 信号只在 **syscall 返回** 时投递(没有中断返回钩子);`ucontext_t` 为自定义
|
||||
紧凑布局,`siglongjmp` 不可用
|
||||
- `futex` 为“查值 + yield”轮询;`poll` 恒返回 0(仅超时语义)
|
||||
- `exit_group(231)` 终止进程内所有线程;进程退出不回收管道/文件引用计数
|
||||
- fork/exec 继承的非管道 fd 标记为两侧共享:close 只清槽位不释放对象(避免
|
||||
交叉释放;代价是泄漏)
|
||||
- 资源上限:8 进程 / 每进程 8 线程 / 16 fd / 8 目录句柄 / 64 VMA / 8 管道
|
||||
- P1 已补:`openat` / `fstat` / `newfstatat` / `getdents64` / `dup` / `fcntl`,
|
||||
私有目录调用已删除
|
||||
- P2 已补:`brk` / `mprotect` / `munmap` / `mremap` / `madvise`,文件映射
|
||||
- P3–P6 已补:启动栈/auxv、`PT_INTERP`/ET_DYN、`execve`、`wait4`、Linux
|
||||
`clone`/`fork`、TLS、时间、管道、信号投递、`reboot(169)`
|
||||
|
||||
### 1.4 维护风险
|
||||
|
||||
两份 `enum Syscall` 靠人工同步。P0 起改为**生成**:
|
||||
|
||||
```text
|
||||
Scripts/syscall_table.py ← 唯一权威表
|
||||
│
|
||||
├─ VKernel/Kernel/intr/syscall.vp [BEGIN/END GENERATED]
|
||||
└─ vpsdk/syscall.vp [BEGIN/END GENERATED]
|
||||
```
|
||||
|
||||
`build.py` 在编译前运行生成器;两边不一致会直接覆盖,不再依赖记忆。
|
||||
|
||||
---
|
||||
|
||||
## 2. 目标 ABI 契约
|
||||
|
||||
### 2.1 调用与返回
|
||||
|
||||
- `rax` = 调用号,参数 `rdi/rsi/rdx/r10/r8/r9`
|
||||
- 成功:返回非负值(字节数、fd、地址、0…)
|
||||
- 失败:返回 `-errno`(`-ENOENT`、`-EINVAL`…),**不再返回 `FRESULT` 正数**
|
||||
- 未实现:`-ENOSYS`
|
||||
- 指针参数越界/为空:`-EFAULT`
|
||||
- 用户态 `errno` 由 glibc 从返回值设置,内核不维护 `errno`
|
||||
|
||||
### 2.2 数据结构
|
||||
|
||||
内核新增 `abi/linux.vp`(内核工程 include 增加仓库根目录),字段布局按 x86-64 Linux;
|
||||
SDK 侧需要结构体时可直接 `import abi.linux`。两者共用同一份定义,避免布局漂移。
|
||||
|
||||
| 类型 | 大小 | 用途 |
|
||||
|---|---:|---|
|
||||
| `abi.linux_stat` | 144 | `stat/fstat/newfstatat` |
|
||||
| `abi.linux_dirent64` | 19+name | `getdents64` |
|
||||
| `abi.linux_timespec` | 16 | 时间 |
|
||||
| `abi.linux_utsname` | 390 | `uname` |
|
||||
| `abi.linux_rlimit` | 16 | `prlimit64` |
|
||||
| `abi.iovec` | 16 | `readv/writev` |
|
||||
|
||||
### 2.3 启动栈(P3)
|
||||
|
||||
glibc 不读 `_start(argc, argv)`,它要求内核按 Linux 方式布置用户栈:
|
||||
|
||||
```text
|
||||
[argc][argv0..argvN][NULL][envp...][NULL][auxv pairs][AT_NULL]
|
||||
```
|
||||
|
||||
必须提供的 auxv:`AT_PHDR`、`AT_PHENT`、`AT_PHNUM`、`AT_ENTRY`、`AT_BASE`、
|
||||
`AT_PAGESZ`、`AT_RANDOM`、`AT_UID/EUID/GID/EGID`、`AT_SECURE=0`、
|
||||
`AT_EXECFN`、`AT_SYSINFO_EHDR`(可选,无 vDSO 时省略)。
|
||||
|
||||
动态链接:`PT_INTERP` 指向的解释器按 ET_DYN 基址(`0x70000000`)加载,
|
||||
主程序 PIE 基址 `0x60000000`,`AT_BASE` 指向解释器,`AT_PHDR/AT_ENTRY`
|
||||
仍指向主程序。系统镜像中需要实际存在该解释器与 libc。
|
||||
|
||||
### 2.4 TLS 与线程(P4)
|
||||
|
||||
- `arch_prctl(ARCH_SET_FS)`:设置每线程 `IA32_FS_BASE`
|
||||
- 线程切换时保存/恢复 `FS_BASE`
|
||||
- `set_tid_address`、`set_robust_list`、`rseq`(可为 `-ENOSYS`)
|
||||
- futex 至少覆盖 `FUTEX_WAIT/FUTEX_WAKE`(glibc pthread 必需)
|
||||
|
||||
### 2.5 兼容口径
|
||||
|
||||
- `uname().sysname` 报 `"Linux"`,`release` 报一个 Linux 风格版本号;真实产品名放在 `version` 字段。这样大量脚本/构建系统不会因 `sysname != Linux` 走特殊分支。
|
||||
- 文件权限位使用固定值:普通文件 `0644`、目录 `0755`;FAT32 没有 uid/gid。
|
||||
|
||||
---
|
||||
|
||||
## 3. 阶段计划
|
||||
|
||||
### P0 — ABI 契约与基础设施(本次完成,待编译验证)
|
||||
|
||||
- [x] `abi/linux.vp`:errno、`O_*`、`AT_*`、`DT_*`、`PROT_*`、`MAP_*`、`ARCH_*`、结构体
|
||||
- [x] 统一错误模型:Linux 号段调用返回 `-errno`;未知调用 `-ENOSYS`
|
||||
(私有 0x1000+ 段在 P6 删除前保留旧返回值)
|
||||
- [x] `open` 采用 Linux `O_*`,内核翻译到 FAT32 `FA_*`
|
||||
- [x] `lseek` 返回新偏移(而不是状态码),支持 64 位 offset(FAT32 上限内)
|
||||
- [x] 新调用:`uname` `getuid/geteuid/getgid/getegid` `getppid` `gettid`
|
||||
`getcwd` `chdir` `access` `faccessat` `readlink` `readlinkat`
|
||||
`set_tid_address` `set_robust_list` `rseq` `prlimit64` `getrandom`
|
||||
- [x] `Process.cwd`(默认 `/`)
|
||||
- [x] syscall 表由 `Scripts/syscall_table.py` 生成,`build.py` 自动同步
|
||||
- [x] `vpsdk` 增加对应包装与常量
|
||||
|
||||
验收:现有应用行为不回退;所有失败路径可观察到负 errno;`Scripts/syscall_table.py --check` 通过。
|
||||
|
||||
### P1 — 文件系统 ABI(本次完成,待编译验证)
|
||||
|
||||
- [x] `openat(257)`(`AT_FDCWD` + 绝对路径 + cwd 相对路径)
|
||||
- [x] `fstat(5)`、`newfstatat(262)`、`statx(332)`(返回 `-ENOSYS`,glibc 回退)
|
||||
- [x] `getdents64(217)` + 目录 fd(`O_DIRECTORY`)
|
||||
- [x] `dup(32)` / `dup2(33)` / `dup3(292)`、`fcntl(72)`(`F_GETFL/F_SETFL/F_DUPFD`)
|
||||
- [x] `pread64(17)` / `pwrite64(18)`
|
||||
- [x] `rename(82)`、`rmdir(84)`、`unlinkat(263)`
|
||||
- [x] 删除私有 `OPENDIR/READDIR/CLOSEDIR`
|
||||
- [x] SDK 的 `opendir/readdir/closedir` 改由 `openat + getdents64 + close` 实现,
|
||||
Terminal 等调用方无需改动
|
||||
|
||||
验收:`ls` 走 `openat + getdents64`;SDK 的 `open` 也统一经 `openat(AT_FDCWD)`;
|
||||
`ld.so` 的 `openat/read/fstat/getdents64` 路径可用(文件映射属于 P2)。
|
||||
|
||||
P1 已知限制:
|
||||
|
||||
- `getdents64` 每次只返回一条记录(glibc 合法,只是多一次 syscall)
|
||||
- `d_ino` 固定 1、`d_off` 固定 0;`telldir/seekdir/rewinddir` 暂不保证
|
||||
- `dirfd != AT_FDCWD` 的相对路径查找返回 `-ENOTSUP`(P3/P6 再补目录 fd 反查)
|
||||
- `statx` 返回 `-ENOSYS`,glibc 回退到 `newfstatat`
|
||||
- `rename` 只支持同一 FAT32 卷内;`dup` 共享文件偏移,最后一次 `close` 才释放对象
|
||||
|
||||
### P2 — 内存管理(本次完成,待编译验证)
|
||||
|
||||
- [x] `brk(12)`:独立 brk 区(`0x40000000` 起),按页增长/收缩
|
||||
- [x] `mmap` 真语义:`MAP_FIXED`、`PROT_*`、失败返回 `MAP_FAILED`(-1)
|
||||
- [x] FAT32 文件映射:把文件内容复制进新页(`MAP_PRIVATE` 语义)
|
||||
- [x] `munmap(11)` 真正解映射并把物理页归还 buddy
|
||||
- [x] `mprotect(10)` 修改 PTE 权限(支持 VMA 拆分)
|
||||
- [x] `mremap(25)`(`MREMAP_MAYMOVE` / `MREMAP_FIXED`)、`madvise(28)`
|
||||
- [x] 每进程 VMA 表(64 项),记录 base/size/prot/kind/flags
|
||||
- [x] SDK 的 `mmap` 把 `MAP_FAILED` 归一化为 0,既有 Explorer/devfs 调用方无需修改
|
||||
|
||||
验收:glibc 的 `malloc` 能使用 brk 主 arena;共享库文件可 mmap 后按段 mprotect;
|
||||
Explorer 的 framebuffer 映射不回退。
|
||||
|
||||
P2 已知限制:
|
||||
|
||||
- 文件映射是“读入页”,不支持 `MAP_SHARED` 回写/`msync`(ld.so 用 `MAP_PRIVATE`)
|
||||
- `PROT_NONE` 仍映射为 present(可读),没有 NX;`PROT_EXEC` 仅记录
|
||||
- `munmap` 只归还数据页,页表页仍留在 paging 池
|
||||
- `brk` 区不在 VMA 表里(glibc 不会对它 munmap)
|
||||
- `mremap` 增长必须带 `MREMAP_MAYMOVE`,通过“新建+拷贝+解映射”实现
|
||||
|
||||
### P3 — 进程、启动栈、解释器(本次完成)
|
||||
|
||||
- [x] 用户栈按 Linux 布局写入 `argc/argv/envp/auxv`
|
||||
(AT_PHDR/PHENT/PHNUM/PAGESZ/ENTRY/BASE/RANDOM/EXECFN/UID/EUID/GID/EGID 等)
|
||||
- [x] `execve(59)` 原型 `(path, argv, envp)`;SDK 保留 `spawn` 走私有 `SPAWN_PROC`
|
||||
- [x] `exit_group(231)` 终止进程内所有线程;`exit(60)` 只退当前线程
|
||||
- [x] `wait4(61)`(`WNOHANG`、状态字、子进程回收)、`getppid`
|
||||
- [x] `clone(56)`:`(flags, stack, ptid, ctid, tls)`;CLONE_VM 建线程,
|
||||
不带 CLONE_VM 时复制地址空间
|
||||
- [x] `fork(57)`:复制用户页 + 启动在父进程 syscall 返回点(RAX=0)
|
||||
- [x] `PT_INTERP` + ET_DYN 基址加载;`AT_BASE` 指向解释器
|
||||
- [x] 旧的 ViperOS `thread_create(func,arg)` 移到私有 `THREAD_CREATE_OLD`
|
||||
|
||||
验收:静态非 PIE、动态 PIE(安装 `ld.so` 与 libc)都可从正确的启动栈进入;
|
||||
fork 子进程在父进程同一返回点继续执行,父进程得到子 pid。
|
||||
|
||||
### P4 — TLS 与线程(本次完成)
|
||||
|
||||
- [x] `arch_prctl(158)`:`SET_FS/GET_FS`(`SET_GS/GET_GS` 返回 `-ENOSYS`)
|
||||
- [x] 线程切换保存/恢复 `IA32_FS_BASE`(独立表,不改 `Thread` 的 600B 布局)
|
||||
- [x] `rseq(334)` 返回 `-ENOSYS`;`set_robust_list` 记录
|
||||
- [x] `futex(202)`:`WAIT/WAKE` 采用“检查值 + sched_yield”轮询实现
|
||||
- [x] `clone(CLONE_VM|CLONE_SETTLS)`:子线程从 post-syscall RIP 开始,FS=tls
|
||||
- [x] `CLONE_CHILD_CLEARTID`:线程退出时写 0,配合 futex 唤醒 join
|
||||
|
||||
验收:`pthread_create/join` 走 Linux clone 路径;TLS 随线程切换。
|
||||
|
||||
### P5 — 时间、信号、IPC(本次完成)
|
||||
|
||||
- [x] `clock_gettime(228)` / `clock_getres(229)`(RTC 秒 + 1ms tick)
|
||||
- [x] `gettimeofday(96)`、`nanosleep(35)`
|
||||
- [x] `readv(19)` / `writev(20)`
|
||||
- [x] `pipe(22)` / `pipe2(293)`(8 个管道 × 4KB,轮询读写)
|
||||
- [x] `poll(7)`(当前固定返回 0 = 超时)
|
||||
- [x] `rt_sigaction(13)` / `rt_sigprocmask(14)` / `kill(62)`:完整登记
|
||||
- [x] 信号投递:syscall 返回前重写用户上下文,进入 handler;
|
||||
`rt_sigreturn(15)` 恢复原上下文与 syscall 返回值
|
||||
- [ ] `ppoll` / `select` / `epoll`
|
||||
|
||||
验收:`signal()`/`sigaction()` 注册的 handler 能在下一次 syscall 返回时执行;
|
||||
`raise()`/`kill(getpid(), sig)` 可触发。
|
||||
|
||||
### P6 — 清理私有段(部分完成)
|
||||
|
||||
- [x] `reboot(169)`:校验 Linux magic,支持 RESTART / POWER_OFF
|
||||
- [x] SDK 的 `process.reboot/shutdown` 改用 `reboot(169)`
|
||||
- [~] 私有 `LOAD_SO/SO_CALL1` 保留(内核启动仍加载 `slog.so`,用户态链接器未实现)
|
||||
- [~] 私有 `SPAWN_PROC` / `THREAD_CREATE_OLD` 保留(Terminal 与旧 SDK)
|
||||
- [~] 私有 `REBOOT/SHUTDOWN` 保留作为兼容别名
|
||||
- [ ] 用户态动态链接器 + 0x1000+ 段清空
|
||||
|
||||
验收:Linux `reboot` 可用;私有段尚未清零(受用户态加载器缺失限制)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 调用迁移表
|
||||
|
||||
`P0` = 本次;`—` 表示尚未实现(内核统一返回 `-ENOSYS`)。
|
||||
|
||||
| 号 | Linux | ViperOS | 状态 | 备注 |
|
||||
|---:|---|---|---|---|
|
||||
| 0 | read | READ | P0 | 字节数 / `-errno` |
|
||||
| 1 | write | WRITE | P0 | 同上 |
|
||||
| 2 | open | OPEN | P0 | Linux `O_*`,内核翻译 `FA_*` |
|
||||
| 3 | close | CLOSE | P0 | 0 / `-EBADF` |
|
||||
| 4 | stat | STAT | P0 | 保留私有 `fileinfo`(兼容旧 SDK) |
|
||||
| 5 | fstat | FSTAT | P1 | `struct stat` |
|
||||
| 7 | poll | POLL | P5 | 固定返回 0(超时) |
|
||||
| 8 | lseek | SEEK | P0 | 返回新偏移 |
|
||||
| 9 | mmap | MMAP | P2 | 匿名/文件/设备三种;失败 `MAP_FAILED` |
|
||||
| 10 | mprotect | MPROTECT | P2 | |
|
||||
| 11 | munmap | MUNMAP | P2 | |
|
||||
| 12 | brk | BRK | P2 | |
|
||||
| 13 | rt_sigaction | RT_SIGACTION | P5 | 登记 + 投递 |
|
||||
| 14 | rt_sigprocmask | RT_SIGPROCMASK | P5 | 支持 block/unblock/setmask |
|
||||
| 15 | rt_sigreturn | RT_SIGRETURN | P5 | 恢复原上下文 |
|
||||
| 16 | ioctl | IOCTL | P0 | devfs 子集 |
|
||||
| 17 | pread64 | PREAD64 | P1 | |
|
||||
| 18 | pwrite64 | PWRITE64 | P1 | |
|
||||
| 19 | readv | READV | P5 | |
|
||||
| 20 | writev | WRITEV | P5 | |
|
||||
| 21 | access | ACCESS | P0 | |
|
||||
| 22 | pipe | PIPE | P5 | 8 × 4KB,轮询 |
|
||||
| 24 | sched_yield | YIELD | P0 | |
|
||||
| 25 | mremap | MREMAP | P2 | 增长需 `MREMAP_MAYMOVE` |
|
||||
| 28 | madvise | MADVISE | P2 | 空操作 |
|
||||
| 32 | dup | DUP | P1 | 末次 close 才释放对象 |
|
||||
| 33 | dup2 | DUP2 | P1 | |
|
||||
| 35 | nanosleep | NANOSLEEP | P5 | 1ms 精度 |
|
||||
| 39 | getpid | GETPID | P0 | |
|
||||
| 56 | clone | CLONE | P3 | Linux clone;CLONE_VM 建线程 |
|
||||
| 57 | fork | FORK | P3 | 复制用户地址空间 |
|
||||
| 59 | execve | SPAWN | P3 | 新进程 + 退出调用者 |
|
||||
| 60 | exit | THREAD_EXIT | P0 | |
|
||||
| 61 | wait4 | WAIT4 | P3 | 支持 WNOHANG |
|
||||
| 62 | kill | KILL | P5 | 待下次 syscall 投递 |
|
||||
| 63 | uname | UNAME | P0 | |
|
||||
| 72 | fcntl | FCNTL | P1 | |
|
||||
| 79 | getcwd | GETCWD | P0 | |
|
||||
| 80 | chdir | CHDIR | P0 | |
|
||||
| 82 | rename | RENAME | P1 | |
|
||||
| 83 | mkdir | MKDIR | P0 | |
|
||||
| 84 | rmdir | RMDIR | P1 | |
|
||||
| 87 | unlink | REMOVE | P0 | |
|
||||
| 89 | readlink | READLINK | P0 | 当前恒 `-EINVAL` |
|
||||
| 96 | gettimeofday | GETTIMEOFDAY | P5 | RTC 秒 |
|
||||
| 102 | getuid | GETUID | P0 | 恒 0 |
|
||||
| 104 | getgid | GETGID | P0 | 恒 0 |
|
||||
| 107 | geteuid | GETEUID | P0 | 恒 0 |
|
||||
| 108 | getegid | GETEGID | P0 | 恒 0 |
|
||||
| 110 | getppid | GETPPID | P0 | 恒 0 |
|
||||
| 158 | arch_prctl | ARCH_PRCTL | P4 | SET_FS/GET_FS |
|
||||
| 169 | reboot | REBOOT_LINUX | P6 | magic 校验;RESTART/POWER_OFF |
|
||||
| 186 | gettid | GETTID | P0 | 返回线程 tid |
|
||||
| 202 | futex | FUTEX | P4 | 轮询 WAIT/WAKE |
|
||||
| 217 | getdents64 | GETDENTS64 | P1 | 每次返回一条记录 |
|
||||
| 218 | set_tid_address | SET_TID_ADDRESS | P0 | |
|
||||
| 228 | clock_gettime | CLOCK_GETTIME | P5 | RTC/1ms tick |
|
||||
| 229 | clock_getres | CLOCK_GETRES | P5 | 1ms |
|
||||
| 231 | exit_group | EXIT | P3 | 终止进程内所有线程 |
|
||||
| 257 | openat | OPENAT | P1 | |
|
||||
| 262 | newfstatat | NEWFSTATAT | P1 | |
|
||||
| 263 | unlinkat | UNLINKAT | P1 | |
|
||||
| 267 | readlinkat | READLINKAT | P0 | 当前恒 `-EINVAL` |
|
||||
| 269 | faccessat | FACCESSAT | P0 | |
|
||||
| 273 | set_robust_list | SET_ROBUST_LIST | P0 | 仅记录 |
|
||||
| 292 | dup3 | DUP3 | P1 | flags 忽略 |
|
||||
| 293 | pipe2 | PIPE2 | P5 | flags 仅记录 |
|
||||
| 302 | prlimit64 | PRLIMIT64 | P0 | 只读限制 |
|
||||
| 318 | getrandom | GETRANDOM | P0 | 软件 DRNG |
|
||||
| 332 | statx | — | P1 | `-ENOSYS`,glibc 回退 |
|
||||
| 334 | rseq | RSEQ | P0 | `-ENOSYS` |
|
||||
| 0x1006 | — | LOAD_SO | P6 删除 | |
|
||||
| 0x1007 | — | SO_CALL1 | P6 删除 | |
|
||||
| 0x1008 | — | REBOOT | P6 保留别名 | |
|
||||
| 0x1009 | — | SHUTDOWN | P6 保留别名 | |
|
||||
| 0x100A | — | SPAWN_PROC | P6 保留 | Terminal `run` 用 |
|
||||
| 0x100B | — | THREAD_CREATE_OLD | P6 保留 | 旧 `thread_create(func,arg)` |
|
||||
|
||||
---
|
||||
|
||||
## 5. 测试与验收
|
||||
|
||||
1. **ABI 单元测试**(用户态小程序)
|
||||
- 每个调用至少覆盖成功 + 一个错误路径,断言返回值为 `-errno`
|
||||
- 结构体偏移/大小与 Linux 对照(编译期 `__sizeof__`)
|
||||
2. **`Scripts/syscall_table.py --check`**
|
||||
- CI/本地构建前检查两份表是否与权威表一致
|
||||
3. **glibc 里程碑**
|
||||
- M1:静态 `hello`(write/exit/brk/mmap/arch_prctl)
|
||||
- M2:动态 `hello`(`ld.so`、`openat`、`mmap`、`mprotect`)
|
||||
- M3:`pthread` + `malloc` 压力测试
|
||||
- M4:`ls`/`cat`/`sh` 等 coreutils
|
||||
4. **回归**
|
||||
- 现有 7 个应用(Terminal / Explorer / Scene3D / Gargantua / FontDemo / HelloWorld / ImageSee)必须继续运行
|
||||
|
||||
### 5.1 已完成的冒烟验证(2026-10-28,QEMU)
|
||||
|
||||
- 7 个内置应用全部启动,进入主循环,无异常
|
||||
- ImageSee 在同一启动中解码 PNG/JPEG 并在窗口显示(见 `docs/imagesee.md`)
|
||||
- 临时测试程序调用 `fork()`:父进程得到子 pid,子进程在同一返回点继续运行
|
||||
(`getpid/getppid` 正确),`wait4` 返回子 pid 且状态为 `0x700`(exit code 7)
|
||||
- 信号:`rt_sigaction(SIGUSR1)` + `kill(self)` → handler 在 `kill` 返回时执行,
|
||||
`kill` 仍返回 0;`SIGUSR2` 被 `rt_sigprocmask` 阻塞时不投递,解除阻塞后的下一次
|
||||
syscall 返回时投递
|
||||
- 无人值守运行:`python build.py --headless --runsecs=45`(`-display none`,
|
||||
45 秒后自动结束 QEMU,串口仍写入 `serial.log`)
|
||||
|
||||
---
|
||||
|
||||
## 6. 风险与已定决策
|
||||
|
||||
| 风险 | 处理 |
|
||||
|---|---|
|
||||
| 错误码改造影响旧应用 | SDK 已按 `<0` 判断;`FRESULT` 正数路径一并改掉,旧应用需回归测试 |
|
||||
| 两份表再次漂移 | 生成器 + `build.py` 前缀同步;禁止手改生成段 |
|
||||
| FAT32 无 inode/权限/符号链接 | `struct stat` 用固定值;`readlink` 一律 `-EINVAL`;`chmod` 后置 |
|
||||
| `mprotect` 未实现时 RELRO 失效 | P2 前在文档标注为“安全降级”,不假装成功 |
|
||||
| `getrandom` 无硬件熵 | 软件 xorshift + 启动期混合,仅用于 canary/非密码学场景 |
|
||||
| `uname` 报 Linux | 提升兼容性;`version` 字段保留 ViperOS 标识 |
|
||||
| 用户态栈布局改动大 | P3 单独提交,先静态后动态,避免与内存阶段交叉 |
|
||||
+70
-28
@@ -1,23 +1,31 @@
|
||||
from . import syscall as syscall
|
||||
import string
|
||||
|
||||
|
||||
define O_RDONLY = 0x01
|
||||
define O_WRONLY = 0x02
|
||||
define O_RDWR = 0x03
|
||||
define O_CREAT = 0x04
|
||||
define O_TRUNC = 0x08
|
||||
define O_APPEND = 0x30
|
||||
# Linux x86-64 open flags. The kernel translates these to FatFs FA_* modes;
|
||||
# never pass FatFs values from user space.
|
||||
define O_RDONLY = 0
|
||||
define O_WRONLY = 1
|
||||
define O_RDWR = 2
|
||||
define O_CREAT = 64
|
||||
define O_EXCL = 128
|
||||
define O_TRUNC = 512
|
||||
define O_APPEND = 1024
|
||||
define O_DIRECTORY = 65536
|
||||
define O_CLOEXEC = 524288
|
||||
|
||||
define SEEK_SET = 0
|
||||
define SEEK_CUR = 1
|
||||
define SEEK_END = 2
|
||||
|
||||
define DT_UNKNOWN = 0
|
||||
define DT_FILE = 1
|
||||
define DT_DIR = 2
|
||||
define DT_REG = 8
|
||||
define DT_DIR = 4
|
||||
define DT_FILE = 8 # legacy alias
|
||||
|
||||
def open(path: const str, flags: uint32) -> int:
|
||||
return int(syscall._syscall3(uint64(syscall.Syscall.OPEN), uint64(path), uint64(flags), 0))
|
||||
"""SDK open() goes through openat(AT_FDCWD) so relative paths honor cwd."""
|
||||
return syscall.openat(syscall.AT_FDCWD, path, flags, 0)
|
||||
|
||||
def close(fd: int) -> int:
|
||||
return int(syscall._syscall1(uint64(syscall.Syscall.CLOSE), uint64(fd)))
|
||||
@@ -29,20 +37,19 @@ def write(fd: int, buf: rawptr, count: uint32) -> int:
|
||||
return int(syscall._syscall3(uint64(syscall.Syscall.WRITE), uint64(fd), uint64(buf), uint64(count)))
|
||||
|
||||
def seek(fd: int, offset: uint32, whence: uint32 = 0) -> int:
|
||||
"""lseek: returns the new offset (>= 0) or -errno."""
|
||||
return int(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), uint64(offset), uint64(whence)))
|
||||
|
||||
def tell(fd: int) -> uint32:
|
||||
"""当前位置 = lseek(fd, 0, SEEK_CUR)(TELL 私有 syscall 已删除)。"""
|
||||
return uint32(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_CUR)))
|
||||
def tell(fd: int) -> int:
|
||||
"""当前偏移 = lseek(fd, 0, SEEK_CUR)。"""
|
||||
return int(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_CUR)))
|
||||
|
||||
def fsize(fd: int) -> uint32:
|
||||
"""文件大小:SIZE 私有 syscall 已删除,改用 lseek 往返(不改变最终位置)。
|
||||
|
||||
三次 lseek:记住当前位置 → 跳到末尾拿到大小 → 复位。多两次 syscall,换掉一个私有号。
|
||||
"""
|
||||
cur: uint32 = uint32(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_CUR)))
|
||||
end: uint32 = uint32(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_END)))
|
||||
syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), uint64(cur), uint64(SEEK_SET))
|
||||
def fsize(fd: int) -> int:
|
||||
"""文件大小 = lseek(fd, 0, SEEK_END),随后恢复原位置。"""
|
||||
cur: int = tell(fd)
|
||||
end: int = int(syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), 0, uint64(SEEK_END)))
|
||||
if cur >= 0:
|
||||
syscall._syscall3(uint64(syscall.Syscall.SEEK), uint64(fd), uint64(cur), uint64(SEEK_SET))
|
||||
return end
|
||||
|
||||
def mkdir(path: const str) -> int:
|
||||
@@ -52,13 +59,46 @@ def remove(path: const str) -> int:
|
||||
return int(syscall._syscall3(uint64(syscall.Syscall.REMOVE), uint64(path), 0, 0))
|
||||
|
||||
def opendir(path: const str) -> int:
|
||||
return int(syscall._syscall3(uint64(syscall.Syscall.OPENDIR), uint64(path), 0, 0))
|
||||
"""Linux opendir: openat(O_RDONLY|O_DIRECTORY), returns a fd."""
|
||||
return syscall.openat(syscall.AT_FDCWD, path, O_RDONLY | O_DIRECTORY, 0)
|
||||
|
||||
def readdir(dd: int, info: rawptr) -> int:
|
||||
return int(syscall._syscall3(uint64(syscall.Syscall.READDIR), uint64(dd), uint64(info), 0))
|
||||
def readdir(fd: int, info: rawptr) -> int:
|
||||
"""Fill the legacy fileinfo layout from one getdents64 record.
|
||||
|
||||
def closedir(dd: int) -> int:
|
||||
return int(syscall._syscall1(uint64(syscall.Syscall.CLOSEDIR), uint64(dd)))
|
||||
Returns 0 with an entry, -ENOENT at end-of-directory, or another -errno.
|
||||
"""
|
||||
if info is None: return -22 # -EINVAL
|
||||
buf: [512]char
|
||||
n: int = syscall.getdents64(fd, &buf[0], uint64(512))
|
||||
if n < 0: return n
|
||||
if n == 0: return -2 # -ENOENT: end of directory
|
||||
string.memset(info, 0, 288) # sizeof(fileinfo)
|
||||
dt: int = int(uint8(buf[18]))
|
||||
attr: uint8 = 0x20
|
||||
if dt == DT_DIR: attr = 0x10
|
||||
ap: uint8* = uint8*(uint64(info) + 4)
|
||||
ap[0] = attr
|
||||
dst: char* = char*(uint64(info) + 16)
|
||||
string.strcpy(dst, &buf[19])
|
||||
return 0
|
||||
|
||||
def closedir(fd: int) -> int:
|
||||
return close(fd)
|
||||
|
||||
def openat(dirfd: int, path: const str, flags: uint32, mode: uint32 = 0) -> int:
|
||||
return syscall.openat(dirfd, path, flags, mode)
|
||||
|
||||
def rename(old_path: const str, new_path: const str) -> int:
|
||||
return syscall.rename(old_path, new_path)
|
||||
|
||||
def rmdir(path: const str) -> int:
|
||||
return syscall.rmdir(path)
|
||||
|
||||
def unlinkat(dirfd: int, path: const str, flags: uint32 = 0) -> int:
|
||||
return syscall.unlinkat(dirfd, path, flags)
|
||||
|
||||
def fcntl(fd: int, cmd: uint32, arg: uint64 = 0) -> int:
|
||||
return syscall.fcntl(fd, cmd, arg)
|
||||
|
||||
def stat(path: const str, info: rawptr) -> int:
|
||||
return int(syscall._syscall3(uint64(syscall.Syscall.STAT), uint64(path), uint64(info), 0))
|
||||
@@ -78,7 +118,9 @@ class FileIO:
|
||||
self._fd = fd
|
||||
self._flags = flags
|
||||
self._pos = 0
|
||||
self._size = fsize(fd)
|
||||
sz: int = fsize(fd)
|
||||
if sz < 0: sz = 0
|
||||
self._size = uint32(sz)
|
||||
self._closed = 0
|
||||
|
||||
def read(self, buf: rawptr, count: uint32) -> int:
|
||||
@@ -100,8 +142,8 @@ class FileIO:
|
||||
def seek(self, offset: uint32) -> int:
|
||||
if self._closed: return -1
|
||||
res: int = seek(self._fd, offset)
|
||||
if res == 0:
|
||||
self._pos = offset
|
||||
if res >= 0:
|
||||
self._pos = uint32(res)
|
||||
return res
|
||||
|
||||
def tell(self) -> uint32:
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# ImageSee shared types.
|
||||
|
||||
import vpsdk.stdlib as stdlib
|
||||
from stdint import *
|
||||
|
||||
define IMAGE_FMT_UNKNOWN = 0
|
||||
define IMAGE_FMT_PNG = 1
|
||||
define IMAGE_FMT_JPEG = 2
|
||||
|
||||
class Image:
|
||||
width: int
|
||||
height: int
|
||||
format: int
|
||||
# Packed 0xAARRGGBB, matching the window/FBCanvas pixel format.
|
||||
pixels: uint32*
|
||||
|
||||
def new_image(w: int, h: int, fmt: int) -> Image*:
|
||||
if w <= 0 or h <= 0: return None
|
||||
if w > 16384 or h > 16384: return None
|
||||
img: Image* = Image*(stdlib.malloc(uint64(Image.__sizeof__())))
|
||||
if img is None: return None
|
||||
px: uint32* = uint32*(stdlib.malloc(uint64(w * h * 4)))
|
||||
if px is None: return None
|
||||
img.width = w
|
||||
img.height = h
|
||||
img.format = fmt
|
||||
img.pixels = px
|
||||
return img
|
||||
|
||||
def free_image(img: Image*):
|
||||
# stdlib.free() is a no-op over the bump mmap heap; keep the API for callers.
|
||||
if img is None: return
|
||||
img.pixels = None
|
||||
img.width = 0
|
||||
img.height = 0
|
||||
|
||||
def pixel_at(img: Image*, x: int, y: int) -> uint32:
|
||||
if img is None: return uint32(0)
|
||||
if x < 0 or y < 0 or x >= img.width or y >= img.height: return uint32(0)
|
||||
return img.pixels[y * img.width + x]
|
||||
|
||||
def red_of(c: uint32) -> int:
|
||||
return int((c >> 16) & uint32(0xFF))
|
||||
|
||||
def green_of(c: uint32) -> int:
|
||||
return int((c >> 8) & uint32(0xFF))
|
||||
|
||||
def blue_of(c: uint32) -> int:
|
||||
return int(c & uint32(0xFF))
|
||||
|
||||
def alpha_of(c: uint32) -> int:
|
||||
return int((c >> 24) & uint32(0xFF))
|
||||
|
||||
def make_pixel(r: int, g: int, b: int, a: int = 255) -> uint32:
|
||||
v: uint32 = (uint32(a & 0xFF) << 24) | (uint32(r & 0xFF) << 16)
|
||||
v = v | (uint32(g & 0xFF) << 8) | uint32(b & 0xFF)
|
||||
return v
|
||||
@@ -0,0 +1,67 @@
|
||||
# ImageSee: small PNG/JPEG decoder library for ViperOS user space.
|
||||
#
|
||||
# img = imagesee.load("/IMAGES/SAMPLE.PNG")
|
||||
# if img is not None:
|
||||
# ... img.pixels[y * img.width + x] is 0xAARRGGBB ...
|
||||
#
|
||||
# `load` reads the whole file into memory and sniffs the container format;
|
||||
# `decode_memory` is available when the caller already has the bytes.
|
||||
|
||||
import vpsdk.imagesee.image as image
|
||||
import vpsdk.imagesee.png as png
|
||||
import vpsdk.imagesee.jpeg as jpeg
|
||||
import vpsdk.fs as fs
|
||||
import vpsdk.stdlib as stdlib
|
||||
from stdint import *
|
||||
|
||||
define IMAGESEE_MAX_FILE = 64 * 1024 * 1024
|
||||
|
||||
def decode_memory(data: uint8*, n: uint64) -> image.Image*:
|
||||
if data is None or n < 8: return None
|
||||
if int(data[0]) == 0x89 and int(data[1]) == 0x50:
|
||||
if int(data[2]) == 0x4E and int(data[3]) == 0x47:
|
||||
return png.decode(data, n)
|
||||
if int(data[0]) == 0xFF and int(data[1]) == 0xD8:
|
||||
return jpeg.decode(data, n)
|
||||
return None
|
||||
|
||||
def load(path: const str) -> image.Image*:
|
||||
fd: int = fs.open(path, fs.O_RDONLY)
|
||||
if fd < 0: return None
|
||||
sz: int = fs.fsize(fd)
|
||||
if sz <= 0 or uint64(sz) > uint64(IMAGESEE_MAX_FILE):
|
||||
fs.close(fd)
|
||||
return None
|
||||
buf: uint8* = uint8*(stdlib.malloc(uint64(sz)))
|
||||
if buf is None:
|
||||
fs.close(fd)
|
||||
return None
|
||||
got: uint64 = 0
|
||||
while got < uint64(sz):
|
||||
n: int = fs.read(fd, rawptr(uint64(buf) + got), uint32(uint64(sz) - got))
|
||||
if n <= 0: break
|
||||
got = got + uint64(n)
|
||||
fs.close(fd)
|
||||
if got != uint64(sz): return None
|
||||
return decode_memory(buf, uint64(sz))
|
||||
|
||||
def last_error() -> int:
|
||||
"""Last JPEG decoder failure stage (0 = not started; 7 = render done)."""
|
||||
return jpeg.last_error()
|
||||
|
||||
def format_name(img: image.Image*) -> const str:
|
||||
if img is None: return "none"
|
||||
if img.format == image.IMAGE_FMT_PNG: return "png"
|
||||
if img.format == image.IMAGE_FMT_JPEG: return "jpeg"
|
||||
return "unknown"
|
||||
|
||||
# 32-bit FNV-1a over packed pixels; handy for regression checks.
|
||||
def checksum(img: image.Image*) -> uint32:
|
||||
if img is None: return uint32(0)
|
||||
h: uint32 = uint32(2166136261)
|
||||
n: int = img.width * img.height
|
||||
i: int = 0
|
||||
while i < n:
|
||||
h = (h ^ img.pixels[i]) * uint32(16777619)
|
||||
i = i + 1
|
||||
return h
|
||||
@@ -0,0 +1,261 @@
|
||||
# DEFLATE / zlib decompression (RFC 1950 + RFC 1951).
|
||||
#
|
||||
# Written for PNG's IDAT stream: self-contained, no allocation inside the
|
||||
# decoder (the caller provides the output buffer), canonical Huffman tables
|
||||
# built the same way as the Brotli decoder in this SDK.
|
||||
|
||||
define INF_MAXBITS = 15
|
||||
define INF_MAXCODES = 288
|
||||
define INF_MAXDIST = 32
|
||||
|
||||
class InfHuff:
|
||||
count: [INF_MAXBITS + 1]int
|
||||
symbol: [INF_MAXCODES]int
|
||||
|
||||
# Length codes 257..285 and distance codes 0..29.
|
||||
_lbase: [29]int = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
|
||||
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258]
|
||||
_lextra: [29]int = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
|
||||
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0]
|
||||
_dbase: [30]int = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
|
||||
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
|
||||
8193, 12289, 16385, 24577]
|
||||
_dextra: [30]int = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
|
||||
7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13]
|
||||
_clorder: [19]int = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]
|
||||
|
||||
_inf_src: uint8* = None
|
||||
_inf_len: uint64 = 0
|
||||
_inf_pos: uint64 = 0
|
||||
_inf_bitbuf: uint32 = 0
|
||||
_inf_bitcnt: int = 0
|
||||
_inf_out: uint8* = None
|
||||
_inf_cap: uint64 = 0
|
||||
_inf_got: uint64 = 0
|
||||
_inf_err: int = 0
|
||||
|
||||
_inf_lens: [320]uint8
|
||||
_inf_lit: InfHuff
|
||||
_inf_dist: InfHuff
|
||||
_inf_clen: InfHuff
|
||||
|
||||
def _inf_need(n: int) -> uint32:
|
||||
global _inf_bitbuf, _inf_bitcnt, _inf_pos, _inf_err
|
||||
while _inf_bitcnt < n:
|
||||
if _inf_pos >= _inf_len:
|
||||
_inf_err = 1
|
||||
return uint32(0)
|
||||
_inf_bitbuf = _inf_bitbuf | (uint32(_inf_src[_inf_pos]) << _inf_bitcnt)
|
||||
_inf_pos = _inf_pos + 1
|
||||
_inf_bitcnt = _inf_bitcnt + 8
|
||||
v: uint32 = _inf_bitbuf & ((uint32(1) << uint32(n)) - uint32(1))
|
||||
_inf_bitbuf = _inf_bitbuf >> uint32(n)
|
||||
_inf_bitcnt = _inf_bitcnt - n
|
||||
return v
|
||||
|
||||
def _inf_decode(h: InfHuff*) -> int:
|
||||
global _inf_err
|
||||
code: int = 0
|
||||
first: int = 0
|
||||
index: int = 0
|
||||
ln: int = 1
|
||||
while ln <= INF_MAXBITS:
|
||||
code = code | int(_inf_need(1))
|
||||
cnt: int = h.count[ln]
|
||||
if code - first < cnt:
|
||||
return h.symbol[index + (code - first)]
|
||||
index = index + cnt
|
||||
first = (first + cnt) << 1
|
||||
code = code << 1
|
||||
ln = ln + 1
|
||||
_inf_err = 1
|
||||
return -1
|
||||
|
||||
def _inf_build(h: InfHuff*, lens: uint8*, n: int) -> int:
|
||||
i: int = 0
|
||||
while i <= INF_MAXBITS:
|
||||
h.count[i] = 0
|
||||
i = i + 1
|
||||
i = 0
|
||||
while i < n:
|
||||
ln8: uint8 = lens[i]
|
||||
if ln8 != 0:
|
||||
if int(ln8) > INF_MAXBITS: return -1
|
||||
h.count[int(ln8)] = h.count[int(ln8)] + 1
|
||||
i = i + 1
|
||||
offs: [INF_MAXBITS + 2]int
|
||||
offs[1] = 0
|
||||
ln: int = 1
|
||||
while ln <= INF_MAXBITS:
|
||||
offs[ln + 1] = offs[ln] + h.count[ln]
|
||||
ln = ln + 1
|
||||
i = 0
|
||||
while i < n:
|
||||
ln = int(lens[i])
|
||||
if ln != 0:
|
||||
h.symbol[offs[ln]] = i
|
||||
offs[ln] = offs[ln] + 1
|
||||
i = i + 1
|
||||
return 0
|
||||
|
||||
def _inf_put(b: int) -> int:
|
||||
global _inf_got, _inf_err
|
||||
if _inf_got >= _inf_cap:
|
||||
_inf_err = 1
|
||||
return -1
|
||||
_inf_out[_inf_got] = uint8(b & 0xFF)
|
||||
_inf_got = _inf_got + 1
|
||||
return 0
|
||||
|
||||
def _inf_stored() -> int:
|
||||
global _inf_bitbuf, _inf_bitcnt, _inf_pos
|
||||
# Drop the partial byte and read LEN/NLEN directly from the byte stream.
|
||||
_inf_bitbuf = uint32(0)
|
||||
_inf_bitcnt = 0
|
||||
if _inf_pos + 4 > _inf_len: return -1
|
||||
lenv: int = int(_inf_src[_inf_pos]) | (int(_inf_src[_inf_pos + 1]) << 8)
|
||||
nlen: int = int(_inf_src[_inf_pos + 2]) | (int(_inf_src[_inf_pos + 3]) << 8)
|
||||
_inf_pos = _inf_pos + 4
|
||||
if (lenv ^ 0xFFFF) != nlen: return -1
|
||||
if _inf_pos + uint64(lenv) > _inf_len: return -1
|
||||
i: int = 0
|
||||
while i < lenv:
|
||||
if _inf_put(int(_inf_src[_inf_pos])) != 0: return -1
|
||||
_inf_pos = _inf_pos + 1
|
||||
i = i + 1
|
||||
return 0
|
||||
|
||||
def _inf_fixed() -> int:
|
||||
i: int = 0
|
||||
while i < 144:
|
||||
_inf_lens[i] = uint8(8)
|
||||
i = i + 1
|
||||
while i < 256:
|
||||
_inf_lens[i] = uint8(9)
|
||||
i = i + 1
|
||||
while i < 280:
|
||||
_inf_lens[i] = uint8(7)
|
||||
i = i + 1
|
||||
while i < 288:
|
||||
_inf_lens[i] = uint8(8)
|
||||
i = i + 1
|
||||
if _inf_build(&_inf_lit, &_inf_lens[0], 288) != 0: return -1
|
||||
i = 0
|
||||
while i < 32:
|
||||
_inf_lens[i] = uint8(5)
|
||||
i = i + 1
|
||||
if _inf_build(&_inf_dist, &_inf_lens[0], 32) != 0: return -1
|
||||
return 0
|
||||
|
||||
def _inf_dynamic() -> int:
|
||||
hlit: int = int(_inf_need(5)) + 257
|
||||
hdist: int = int(_inf_need(5)) + 1
|
||||
hclen: int = int(_inf_need(4)) + 4
|
||||
if hlit > 286 or hdist > 30: return -1
|
||||
i: int = 0
|
||||
while i < 320:
|
||||
_inf_lens[i] = uint8(0)
|
||||
i = i + 1
|
||||
i = 0
|
||||
while i < hclen:
|
||||
_inf_lens[_clorder[i]] = uint8(_inf_need(3))
|
||||
i = i + 1
|
||||
if _inf_build(&_inf_clen, &_inf_lens[0], 19) != 0: return -1
|
||||
n: int = hlit + hdist
|
||||
i = 0
|
||||
while i < n:
|
||||
sym: int = _inf_decode(&_inf_clen)
|
||||
if _inf_err != 0: return -1
|
||||
if sym < 16:
|
||||
_inf_lens[i] = uint8(sym)
|
||||
i = i + 1
|
||||
elif sym == 16:
|
||||
if i == 0: return -1
|
||||
rep: int = 3 + int(_inf_need(2))
|
||||
prev: uint8 = _inf_lens[i - 1]
|
||||
while rep > 0 and i < n:
|
||||
_inf_lens[i] = prev
|
||||
i = i + 1
|
||||
rep = rep - 1
|
||||
elif sym == 17:
|
||||
rep: int = 3 + int(_inf_need(3))
|
||||
while rep > 0 and i < n:
|
||||
_inf_lens[i] = uint8(0)
|
||||
i = i + 1
|
||||
rep = rep - 1
|
||||
elif sym == 18:
|
||||
rep: int = 11 + int(_inf_need(7))
|
||||
while rep > 0 and i < n:
|
||||
_inf_lens[i] = uint8(0)
|
||||
i = i + 1
|
||||
rep = rep - 1
|
||||
else:
|
||||
return -1
|
||||
if _inf_build(&_inf_lit, &_inf_lens[0], hlit) != 0: return -1
|
||||
if _inf_build(&_inf_dist, &_inf_lens[hlit], hdist) != 0: return -1
|
||||
return 0
|
||||
|
||||
def _inf_codes() -> int:
|
||||
while True:
|
||||
sym: int = _inf_decode(&_inf_lit)
|
||||
if _inf_err != 0: return -1
|
||||
if sym < 256:
|
||||
if _inf_put(sym) != 0: return -1
|
||||
elif sym == 256:
|
||||
return 0
|
||||
elif sym >= 257 and sym <= 285:
|
||||
li: int = sym - 257
|
||||
length: int = _lbase[li] + int(_inf_need(_lextra[li]))
|
||||
dsym: int = _inf_decode(&_inf_dist)
|
||||
if _inf_err != 0: return -1
|
||||
if dsym < 0 or dsym >= 30: return -1
|
||||
dist: int = _dbase[dsym] + int(_inf_need(_dextra[dsym]))
|
||||
if dist <= 0 or uint64(dist) > _inf_got: return -1
|
||||
i: int = 0
|
||||
while i < length:
|
||||
src_i: uint64 = _inf_got - uint64(dist)
|
||||
if _inf_put(int(_inf_out[src_i])) != 0: return -1
|
||||
i = i + 1
|
||||
else:
|
||||
return -1
|
||||
|
||||
def _inf_blocks() -> int:
|
||||
while True:
|
||||
final: int = int(_inf_need(1))
|
||||
btype: int = int(_inf_need(2))
|
||||
if _inf_err != 0: return -1
|
||||
if btype == 0:
|
||||
if _inf_stored() != 0: return -1
|
||||
elif btype == 1:
|
||||
if _inf_fixed() != 0: return -1
|
||||
if _inf_codes() != 0: return -1
|
||||
elif btype == 2:
|
||||
if _inf_dynamic() != 0: return -1
|
||||
if _inf_codes() != 0: return -1
|
||||
else:
|
||||
return -1
|
||||
if final != 0:
|
||||
return 0
|
||||
|
||||
# Inflate a zlib stream (skips the 2-byte header). Returns bytes written
|
||||
# into `dst`, or -1 on malformed input / output overflow.
|
||||
def zlib_decompress(src: uint8*, src_len: uint64, dst: uint8*, dst_cap: uint64) -> int:
|
||||
global _inf_src, _inf_len, _inf_pos, _inf_bitbuf, _inf_bitcnt
|
||||
global _inf_out, _inf_cap, _inf_got, _inf_err
|
||||
if src is None or dst is None or src_len < 6: return -1
|
||||
cmf: int = int(src[0])
|
||||
flg: int = int(src[1])
|
||||
if (cmf & 0x0F) != 8: return -1
|
||||
if (cmf * 256 + flg) % 31 != 0: return -1
|
||||
if (flg & 0x20) != 0: return -1 # FDICT unsupported
|
||||
_inf_src = src
|
||||
_inf_len = src_len
|
||||
_inf_pos = 2
|
||||
_inf_bitbuf = uint32(0)
|
||||
_inf_bitcnt = 0
|
||||
_inf_out = dst
|
||||
_inf_cap = dst_cap
|
||||
_inf_got = 0
|
||||
_inf_err = 0
|
||||
if _inf_blocks() != 0: return -1
|
||||
return int(_inf_got)
|
||||
@@ -0,0 +1,781 @@
|
||||
# JPEG decoder: baseline/extended sequential (SOF0/SOF1) and progressive
|
||||
# (SOF2), Huffman entropy coding, restart markers, 4:4:4 / 4:2:2 / 4:2:0
|
||||
# chroma subsampling, 8-bit samples. Output is packed 0xAARRGGBB.
|
||||
#
|
||||
# Coefficient arrays are kept in natural (de-zigzagged) order so the IDCT is
|
||||
# a straight 8x8 pass. Progressive scans refine the same arrays.
|
||||
|
||||
import vpsdk.imagesee.image as im
|
||||
import vpsdk.stdlib as stdlib
|
||||
import string
|
||||
import math
|
||||
from stdint import *
|
||||
|
||||
define J_MAX_COMPS = 4
|
||||
|
||||
class JHuff:
|
||||
count: [17]int
|
||||
symbol: [256]int
|
||||
|
||||
class JComp:
|
||||
id: int
|
||||
hs: int
|
||||
vs: int
|
||||
tq: int
|
||||
td: int
|
||||
ta: int
|
||||
bw: int
|
||||
bh: int
|
||||
coef: int16*
|
||||
plane: uint8*
|
||||
|
||||
_j_src: uint8* = None
|
||||
_j_len: uint64 = 0
|
||||
_j_pos: uint64 = 0
|
||||
_j_bitbuf: uint32 = 0
|
||||
_j_bitcnt: int = 0
|
||||
_j_marker: int = -1
|
||||
_j_err: int = 0
|
||||
|
||||
_j_w: int = 0
|
||||
_j_h: int = 0
|
||||
_j_ncomp: int = 0
|
||||
_j_maxh: int = 1
|
||||
_j_maxv: int = 1
|
||||
_j_mcu_cols: int = 0
|
||||
_j_mcu_rows: int = 0
|
||||
_j_progressive: int = 0
|
||||
_j_rgb_mode: int = 0
|
||||
_j_restart: int = 0
|
||||
|
||||
_j_scan_ns: int = 0
|
||||
_j_sc: [4]int
|
||||
_j_sctd: [4]int
|
||||
_j_scta: [4]int
|
||||
_j_ss: int = 0
|
||||
_j_se: int = 0
|
||||
_j_ah: int = 0
|
||||
_j_al: int = 0
|
||||
_j_eobrun: int = 0
|
||||
_j_dcpred: [4]int
|
||||
|
||||
_j_qt: [4 * 64]uint16
|
||||
_j_qt_used: [4]int
|
||||
_j_dc: [4]JHuff
|
||||
_j_ac: [4]JHuff
|
||||
_j_comps: [4]JComp
|
||||
_j_planes_ready: int = 0
|
||||
_j_stage: int = 0
|
||||
_j_scan_no: int = 0
|
||||
_j_cur_mcu: int = 0
|
||||
|
||||
_j_cs: [64]float
|
||||
_j_cw: [8]float
|
||||
_j_trig_ready: int = 0
|
||||
_j_g: [64]float
|
||||
|
||||
_zigzag: [64]int = [
|
||||
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5,
|
||||
12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28,
|
||||
35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
|
||||
58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63
|
||||
]
|
||||
|
||||
def _j_u8(o: uint64) -> int:
|
||||
return int(_j_src[o])
|
||||
|
||||
def last_error() -> int:
|
||||
return _j_stage
|
||||
|
||||
def _j_u16(o: uint64) -> int:
|
||||
return (int(_j_src[o]) << 8) | int(_j_src[o + 1])
|
||||
|
||||
# ---------------------------------------------------------------- bit reader
|
||||
# Returns the next entropy byte with 0xFF00 unstuffing; on a marker it stores
|
||||
# the marker id in _j_marker and returns -1.
|
||||
def _j_byte() -> int:
|
||||
global _j_pos, _j_marker
|
||||
if _j_pos >= _j_len: return -1
|
||||
b: int = int(_j_src[_j_pos])
|
||||
_j_pos = _j_pos + 1
|
||||
if b == 0xFF:
|
||||
while _j_pos < _j_len and int(_j_src[_j_pos]) == 0xFF:
|
||||
_j_pos = _j_pos + 1
|
||||
if _j_pos >= _j_len: return -1
|
||||
m: int = int(_j_src[_j_pos])
|
||||
_j_pos = _j_pos + 1
|
||||
if m == 0x00: return 0xFF
|
||||
_j_marker = m
|
||||
return -1
|
||||
return b
|
||||
|
||||
def _j_bits(n: int) -> uint32:
|
||||
global _j_bitbuf, _j_bitcnt, _j_err
|
||||
if n <= 0: return uint32(0)
|
||||
while _j_bitcnt < n:
|
||||
b: int = _j_byte()
|
||||
if b < 0:
|
||||
_j_err = 1
|
||||
return uint32(0)
|
||||
_j_bitbuf = (_j_bitbuf << 8) | uint32(b)
|
||||
_j_bitcnt = _j_bitcnt + 8
|
||||
_j_bitcnt = _j_bitcnt - n
|
||||
v: uint32 = (_j_bitbuf >> uint32(_j_bitcnt)) & ((uint32(1) << uint32(n)) - uint32(1))
|
||||
return v
|
||||
|
||||
def _j_get_bit() -> int:
|
||||
return int(_j_bits(1))
|
||||
|
||||
def _j_receive(s: int) -> int:
|
||||
if s == 0: return 0
|
||||
v: int = int(_j_bits(s))
|
||||
if v < (1 << (s - 1)):
|
||||
v = v - ((1 << s) - 1)
|
||||
return v
|
||||
|
||||
def _j_huff_build(h: JHuff*, counts: uint8*, vals: uint8*, n: int) -> int:
|
||||
total: int = 0
|
||||
i: int = 1
|
||||
while i <= 16:
|
||||
c: int = int(counts[i - 1])
|
||||
h.count[i] = c
|
||||
total = total + c
|
||||
i = i + 1
|
||||
if total != n: return -1
|
||||
# canonical offsets
|
||||
offs: [18]int
|
||||
offs[1] = 0
|
||||
i = 1
|
||||
while i <= 16:
|
||||
offs[i + 1] = offs[i] + h.count[i]
|
||||
i = i + 1
|
||||
# `vals` is already ordered by code length (JPEG DHT order).
|
||||
i = 0
|
||||
ln: int = 1
|
||||
vi: int = 0
|
||||
while ln <= 16:
|
||||
c = h.count[ln]
|
||||
k: int = 0
|
||||
while k < c:
|
||||
h.symbol[offs[ln] + k] = int(vals[vi])
|
||||
vi = vi + 1
|
||||
k = k + 1
|
||||
ln = ln + 1
|
||||
return 0
|
||||
|
||||
def _j_hdecode(h: JHuff*) -> int:
|
||||
global _j_err
|
||||
code: int = 0
|
||||
first: int = 0
|
||||
index: int = 0
|
||||
ln: int = 1
|
||||
while ln <= 16:
|
||||
code = code | _j_get_bit()
|
||||
cnt: int = h.count[ln]
|
||||
if code - first < cnt:
|
||||
return h.symbol[index + (code - first)]
|
||||
index = index + cnt
|
||||
first = (first + cnt) << 1
|
||||
code = code << 1
|
||||
ln = ln + 1
|
||||
_j_err = 1
|
||||
return 0
|
||||
|
||||
# --------------------------------------------------------------- markers
|
||||
def _j_next_marker() -> int:
|
||||
global _j_pos, _j_marker
|
||||
if _j_marker >= 0:
|
||||
m: int = _j_marker
|
||||
_j_marker = -1
|
||||
return m
|
||||
while _j_pos < _j_len:
|
||||
b: int = int(_j_src[_j_pos])
|
||||
_j_pos = _j_pos + 1
|
||||
if b != 0xFF: continue
|
||||
while _j_pos < _j_len and int(_j_src[_j_pos]) == 0xFF:
|
||||
_j_pos = _j_pos + 1
|
||||
if _j_pos >= _j_len: return -1
|
||||
m = int(_j_src[_j_pos])
|
||||
_j_pos = _j_pos + 1
|
||||
if m == 0x00: continue
|
||||
return m
|
||||
return -1
|
||||
|
||||
def _j_parse_dqt(p: uint64, pend: uint64) -> int:
|
||||
while p < pend:
|
||||
pq: int = int(_j_src[p]) >> 4
|
||||
tq: int = int(_j_src[p]) & 15
|
||||
p = p + 1
|
||||
if tq >= 4: return -1
|
||||
i: int = 0
|
||||
while i < 64:
|
||||
if pq != 0:
|
||||
if p + 1 >= pend: return -1
|
||||
v: int = (int(_j_src[p]) << 8) | int(_j_src[p + 1])
|
||||
p = p + 2
|
||||
else:
|
||||
if p >= pend: return -1
|
||||
v = int(_j_src[p])
|
||||
p = p + 1
|
||||
_j_qt[tq * 64 + _zigzag[i]] = uint16(v)
|
||||
i = i + 1
|
||||
_j_qt_used[tq] = 1
|
||||
return 0
|
||||
|
||||
def _j_parse_dht(p: uint64, pend: uint64) -> int:
|
||||
while p < pend:
|
||||
if p + 17 > pend: return -1
|
||||
tc: int = int(_j_src[p]) >> 4
|
||||
th: int = int(_j_src[p]) & 15
|
||||
p = p + 1
|
||||
if tc > 1 or th > 3: return -1
|
||||
counts: [16]uint8
|
||||
total: int = 0
|
||||
i: int = 0
|
||||
while i < 16:
|
||||
counts[i] = _j_src[p + i]
|
||||
total = total + int(counts[i])
|
||||
i = i + 1
|
||||
p = p + 16
|
||||
if total <= 0 or total > 256 or p + uint64(total) > pend: return -1
|
||||
vals: [256]uint8
|
||||
i = 0
|
||||
while i < total:
|
||||
vals[i] = _j_src[p + i]
|
||||
i = i + 1
|
||||
p = p + uint64(total)
|
||||
if tc == 0:
|
||||
if _j_huff_build(&_j_dc[th], &counts[0], &vals[0], total) != 0: return -1
|
||||
else:
|
||||
if _j_huff_build(&_j_ac[th], &counts[0], &vals[0], total) != 0: return -1
|
||||
return 0
|
||||
|
||||
def _j_parse_sof(p: uint64, pend: uint64, prog: int) -> int:
|
||||
global _j_w, _j_h, _j_ncomp, _j_progressive, _j_maxh, _j_maxv, _j_mcu_cols, _j_mcu_rows, _j_planes_ready
|
||||
if p + 6 > pend: return -1
|
||||
prec: int = int(_j_src[p])
|
||||
h: int = _j_u16(p + 1)
|
||||
w: int = _j_u16(p + 3)
|
||||
nf: int = int(_j_src[p + 5])
|
||||
p = p + 6
|
||||
if prec != 8 or w <= 0 or h <= 0 or nf < 1 or nf > J_MAX_COMPS: return -1
|
||||
if w > 16384 or h > 16384: return -1
|
||||
if p + uint64(nf * 3) > pend: return -1
|
||||
_j_w = w
|
||||
_j_h = h
|
||||
_j_ncomp = nf
|
||||
_j_progressive = prog
|
||||
_j_maxh = 1
|
||||
_j_maxv = 1
|
||||
i: int = 0
|
||||
while i < nf:
|
||||
cid: int = int(_j_src[p + i * 3])
|
||||
hv: int = int(_j_src[p + i * 3 + 1])
|
||||
tq: int = int(_j_src[p + i * 3 + 2])
|
||||
hs: int = hv >> 4
|
||||
vs: int = hv & 15
|
||||
if hs < 1 or hs > 4 or vs < 1 or vs > 4 or tq > 3: return -1
|
||||
_j_comps[i].id = cid
|
||||
_j_comps[i].hs = hs
|
||||
_j_comps[i].vs = vs
|
||||
_j_comps[i].tq = tq
|
||||
_j_comps[i].td = 0
|
||||
_j_comps[i].ta = 0
|
||||
if hs > _j_maxh: _j_maxh = hs
|
||||
if vs > _j_maxv: _j_maxv = vs
|
||||
i = i + 1
|
||||
_j_mcu_cols = (w + 8 * _j_maxh - 1) / (8 * _j_maxh)
|
||||
_j_mcu_rows = (h + 8 * _j_maxv - 1) / (8 * _j_maxv)
|
||||
_j_planes_ready = 0
|
||||
return 0
|
||||
|
||||
def _j_prepare() -> int:
|
||||
global _j_planes_ready
|
||||
if _j_planes_ready != 0: return 0
|
||||
i: int = 0
|
||||
while i < _j_ncomp:
|
||||
comp: JComp* = &_j_comps[i]
|
||||
comp.bw = _j_mcu_cols * comp.hs
|
||||
comp.bh = _j_mcu_rows * comp.vs
|
||||
if comp.bw <= 0 or comp.bh <= 0: return -1
|
||||
if _j_progressive != 0:
|
||||
comp.coef = int16*(stdlib.malloc(uint64(comp.bw * comp.bh * 64 * 2)))
|
||||
if comp.coef is None: return -1
|
||||
string.memset(rawptr(comp.coef), 0, uint64(comp.bw * comp.bh * 64 * 2))
|
||||
else:
|
||||
comp.coef = int16*(stdlib.malloc(uint64(comp.bw * comp.bh * 64 * 2)))
|
||||
if comp.coef is None: return -1
|
||||
string.memset(rawptr(comp.coef), 0, uint64(comp.bw * comp.bh * 64 * 2))
|
||||
i = i + 1
|
||||
_j_planes_ready = 1
|
||||
return 0
|
||||
|
||||
# ------------------------------------------------------------------- blocks
|
||||
def _j_block_seq(c: int, blk: int):
|
||||
comp: JComp* = &_j_comps[c]
|
||||
base: int = blk * 64
|
||||
t: int = _j_hdecode(&_j_dc[comp.td])
|
||||
s: int = t & 15
|
||||
_j_dcpred[c] = _j_dcpred[c] + _j_receive(s)
|
||||
comp.coef[base] = int16(_j_dcpred[c])
|
||||
k: int = 1
|
||||
while k < 64:
|
||||
rs: int = _j_hdecode(&_j_ac[comp.ta])
|
||||
if _j_err != 0: break
|
||||
s = rs & 15
|
||||
r: int = rs >> 4
|
||||
if s == 0:
|
||||
if r == 15:
|
||||
k = k + 16
|
||||
continue
|
||||
break
|
||||
k = k + r
|
||||
if k > 63: break
|
||||
v: int = _j_receive(s)
|
||||
comp.coef[base + _zigzag[k]] = int16(v)
|
||||
k = k + 1
|
||||
|
||||
def _j_block_dc_first(c: int, blk: int):
|
||||
comp: JComp* = &_j_comps[c]
|
||||
t: int = _j_hdecode(&_j_dc[comp.td])
|
||||
s: int = t & 15
|
||||
_j_dcpred[c] = _j_dcpred[c] + _j_receive(s)
|
||||
comp.coef[blk * 64] = int16(_j_dcpred[c] << _j_al)
|
||||
|
||||
def _j_block_dc_refine(c: int, blk: int):
|
||||
comp: JComp* = &_j_comps[c]
|
||||
if _j_get_bit() != 0:
|
||||
comp.coef[blk * 64] = int16(int(comp.coef[blk * 64]) | (1 << _j_al))
|
||||
|
||||
def _j_block_ac_first(c: int, blk: int):
|
||||
global _j_eobrun
|
||||
if _j_eobrun > 0:
|
||||
_j_eobrun = _j_eobrun - 1
|
||||
return
|
||||
comp: JComp* = &_j_comps[c]
|
||||
base: int = blk * 64
|
||||
k: int = _j_ss
|
||||
while k <= _j_se:
|
||||
rs: int = _j_hdecode(&_j_ac[comp.ta])
|
||||
if _j_err != 0: break
|
||||
s: int = rs & 15
|
||||
r: int = rs >> 4
|
||||
if s == 0:
|
||||
if r < 15:
|
||||
_j_eobrun = (1 << r) + int(_j_bits(r)) - 1
|
||||
break
|
||||
k = k + 16
|
||||
else:
|
||||
k = k + r
|
||||
if k > _j_se: break
|
||||
v: int = _j_receive(s)
|
||||
comp.coef[base + _zigzag[k]] = int16(v << _j_al)
|
||||
k = k + 1
|
||||
|
||||
def _j_block_ac_refine(c: int, blk: int):
|
||||
global _j_eobrun, _j_err
|
||||
comp: JComp* = &_j_comps[c]
|
||||
base: int = blk * 64
|
||||
p1: int = 1 << _j_al
|
||||
m1: int = (0 - 1) << _j_al
|
||||
k: int = _j_ss
|
||||
if _j_eobrun == 0:
|
||||
while k <= _j_se:
|
||||
rs: int = _j_hdecode(&_j_ac[comp.ta])
|
||||
if _j_err != 0: break
|
||||
s: int = rs & 15
|
||||
r: int = rs >> 4
|
||||
newv: int = 0
|
||||
zrl: int = 0
|
||||
if s == 0:
|
||||
if r < 15:
|
||||
_j_eobrun = (1 << r) + int(_j_bits(r)) - 1
|
||||
break
|
||||
zrl = 1
|
||||
else:
|
||||
if s != 1:
|
||||
_j_err = 1
|
||||
break
|
||||
if _j_get_bit() != 0: newv = p1
|
||||
else: newv = m1
|
||||
# Advance over r zero coefficients, refining non-zero ones.
|
||||
while k <= _j_se:
|
||||
idx: int = base + _zigzag[k]
|
||||
cv: int = int(comp.coef[idx])
|
||||
if cv != 0:
|
||||
if _j_get_bit() != 0 and (cv & p1) == 0:
|
||||
if cv > 0: comp.coef[idx] = int16(cv + p1)
|
||||
else: comp.coef[idx] = int16(cv + m1)
|
||||
else:
|
||||
if r == 0: break
|
||||
r = r - 1
|
||||
k = k + 1
|
||||
if newv != 0 and k <= _j_se:
|
||||
comp.coef[base + _zigzag[k]] = int16(newv)
|
||||
k = k + 1
|
||||
elif zrl != 0 and k <= _j_se:
|
||||
# ZRL skips 16 zero coefficients; the inner loop leaves us
|
||||
# parked on the 16th one (r == 0), so step past it.
|
||||
k = k + 1
|
||||
else:
|
||||
while k <= _j_se:
|
||||
idx: int = base + _zigzag[k]
|
||||
cv: int = int(comp.coef[idx])
|
||||
if cv != 0:
|
||||
if _j_get_bit() != 0 and (cv & p1) == 0:
|
||||
if cv > 0: comp.coef[idx] = int16(cv + p1)
|
||||
else: comp.coef[idx] = int16(cv + m1)
|
||||
k = k + 1
|
||||
_j_eobrun = _j_eobrun - 1
|
||||
|
||||
# --------------------------------------------------------------------- scan
|
||||
def _j_restart_scan() -> int:
|
||||
global _j_bitbuf, _j_bitcnt, _j_err, _j_eobrun, _j_marker, _j_pos
|
||||
_j_bitbuf = uint32(0)
|
||||
_j_bitcnt = 0
|
||||
_j_err = 0
|
||||
_j_eobrun = 0
|
||||
i: int = 0
|
||||
while i < J_MAX_COMPS:
|
||||
_j_dcpred[i] = 0
|
||||
i = i + 1
|
||||
if _j_marker >= 0:
|
||||
m: int = _j_marker
|
||||
_j_marker = -1
|
||||
if m >= 0xD0 and m <= 0xD7: return 0
|
||||
_j_marker = m
|
||||
return -1
|
||||
while _j_pos < _j_len:
|
||||
b: int = int(_j_src[_j_pos])
|
||||
_j_pos = _j_pos + 1
|
||||
if b != 0xFF: continue
|
||||
while _j_pos < _j_len and int(_j_src[_j_pos]) == 0xFF:
|
||||
_j_pos = _j_pos + 1
|
||||
if _j_pos >= _j_len: return -1
|
||||
m = int(_j_src[_j_pos])
|
||||
_j_pos = _j_pos + 1
|
||||
if m == 0x00: continue
|
||||
if m >= 0xD0 and m <= 0xD7: return 0
|
||||
_j_marker = m
|
||||
return -1
|
||||
return -1
|
||||
|
||||
def _j_decode_block(c: int, blk: int):
|
||||
if _j_progressive == 0:
|
||||
_j_block_seq(c, blk)
|
||||
else:
|
||||
if _j_ss == 0:
|
||||
if _j_ah == 0: _j_block_dc_first(c, blk)
|
||||
else: _j_block_dc_refine(c, blk)
|
||||
else:
|
||||
if _j_ah == 0: _j_block_ac_first(c, blk)
|
||||
else: _j_block_ac_refine(c, blk)
|
||||
|
||||
def _j_scan() -> int:
|
||||
global _j_bitbuf, _j_bitcnt, _j_err, _j_eobrun, _j_cur_mcu, _j_stage
|
||||
_j_bitbuf = uint32(0)
|
||||
_j_bitcnt = 0
|
||||
_j_err = 0
|
||||
_j_eobrun = 0
|
||||
i: int = 0
|
||||
while i < J_MAX_COMPS:
|
||||
_j_dcpred[i] = 0
|
||||
i = i + 1
|
||||
if _j_scan_ns < 1: return -1
|
||||
if _j_scan_ns > 1:
|
||||
total: int = _j_mcu_cols * _j_mcu_rows
|
||||
mcu: int = 0
|
||||
while mcu < total:
|
||||
_j_cur_mcu = mcu
|
||||
if _j_restart > 0 and mcu > 0 and (mcu % _j_restart) == 0:
|
||||
if _j_restart_scan() != 0: return -1
|
||||
si: int = 0
|
||||
while si < _j_scan_ns:
|
||||
c: int = _j_sc[si]
|
||||
comp: JComp* = &_j_comps[c]
|
||||
vy: int = 0
|
||||
while vy < comp.vs:
|
||||
hx: int = 0
|
||||
while hx < comp.hs:
|
||||
bx: int = (mcu % _j_mcu_cols) * comp.hs + hx
|
||||
by: int = (mcu / _j_mcu_cols) * comp.vs + vy
|
||||
_j_decode_block(c, by * comp.bw + bx)
|
||||
if _j_err != 0:
|
||||
_j_stage = 1000 + mcu
|
||||
return -1
|
||||
hx = hx + 1
|
||||
vy = vy + 1
|
||||
si = si + 1
|
||||
mcu = mcu + 1
|
||||
else:
|
||||
c = _j_sc[0]
|
||||
comp = &_j_comps[c]
|
||||
cw: int = (_j_w * comp.hs + _j_maxh - 1) / _j_maxh
|
||||
ch: int = (_j_h * comp.vs + _j_maxv - 1) / _j_maxv
|
||||
bwc: int = (cw + 7) / 8
|
||||
bhc: int = (ch + 7) / 8
|
||||
total = bwc * bhc
|
||||
k: int = 0
|
||||
while k < total:
|
||||
_j_cur_mcu = k
|
||||
if _j_restart > 0 and k > 0 and (k % _j_restart) == 0:
|
||||
if _j_restart_scan() != 0: return -1
|
||||
by: int = k / bwc
|
||||
bx: int = k % bwc
|
||||
_j_decode_block(c, by * comp.bw + bx)
|
||||
if _j_err != 0:
|
||||
_j_stage = 2000 + k
|
||||
return -1
|
||||
k = k + 1
|
||||
_j_bitbuf = uint32(0)
|
||||
_j_bitcnt = 0
|
||||
return 0
|
||||
|
||||
def _j_parse_sos(p: uint64, pend: uint64) -> int:
|
||||
global _j_scan_ns, _j_ss, _j_se, _j_ah, _j_al
|
||||
if p + 4 > pend: return -1
|
||||
ns: int = int(_j_src[p])
|
||||
if ns < 1 or ns > J_MAX_COMPS or p + uint64(3 + ns * 2) > pend: return -1
|
||||
p = p + 1
|
||||
i: int = 0
|
||||
while i < ns:
|
||||
cs: int = int(_j_src[p])
|
||||
td: int = int(_j_src[p + 1]) >> 4
|
||||
ta: int = int(_j_src[p + 1]) & 15
|
||||
ci: int = -1
|
||||
j: int = 0
|
||||
while j < _j_ncomp:
|
||||
if _j_comps[j].id == cs: ci = j
|
||||
j = j + 1
|
||||
if ci < 0: return -1
|
||||
_j_sc[i] = ci
|
||||
_j_sctd[i] = td
|
||||
_j_scta[i] = ta
|
||||
_j_comps[ci].td = td
|
||||
_j_comps[ci].ta = ta
|
||||
p = p + 2
|
||||
i = i + 1
|
||||
ss: int = int(_j_src[p])
|
||||
se: int = int(_j_src[p + 1])
|
||||
ahal: int = int(_j_src[p + 2])
|
||||
_j_scan_ns = ns
|
||||
_j_ss = ss
|
||||
_j_se = se
|
||||
_j_ah = ahal >> 4
|
||||
_j_al = ahal & 15
|
||||
if se > 63 or ss > se: return -1
|
||||
return 0
|
||||
|
||||
# --------------------------------------------------------------------- IDCT
|
||||
def _j_trig():
|
||||
global _j_trig_ready
|
||||
if _j_trig_ready != 0: return
|
||||
x: int = 0
|
||||
while x < 8:
|
||||
u: int = 0
|
||||
while u < 8:
|
||||
_j_cs[x * 8 + u] = float(math.cos(float((2 * x + 1) * u) * 3.141592653589793 / 16.0))
|
||||
u = u + 1
|
||||
x = x + 1
|
||||
_j_cw[0] = float(0.7071067811865476)
|
||||
u = 1
|
||||
while u < 8:
|
||||
_j_cw[u] = float(1.0)
|
||||
u = u + 1
|
||||
_j_trig_ready = 1
|
||||
|
||||
def _j_idct_block(comp: JComp*, bx: int, by: int):
|
||||
qbase: int = comp.tq * 64
|
||||
cbase: int = (by * comp.bw + bx) * 64
|
||||
v: int = 0
|
||||
while v < 8:
|
||||
x: int = 0
|
||||
while x < 8:
|
||||
s: float = float(0.0)
|
||||
u: int = 0
|
||||
while u < 8:
|
||||
s = s + _j_cw[u] * (float(int(comp.coef[cbase + v * 8 + u])) * float(int(_j_qt[qbase + v * 8 + u]))) * _j_cs[x * 8 + u]
|
||||
u = u + 1
|
||||
_j_g[v * 8 + x] = s
|
||||
x = x + 1
|
||||
v = v + 1
|
||||
y: int = 0
|
||||
pw: int = comp.bw * 8
|
||||
while y < 8:
|
||||
x = 0
|
||||
while x < 8:
|
||||
s: float = float(0.0)
|
||||
v = 0
|
||||
while v < 8:
|
||||
s = s + _j_cw[v] * _j_g[v * 8 + x] * _j_cs[y * 8 + v]
|
||||
v = v + 1
|
||||
val: int = int(s * float(0.25) + float(128.5))
|
||||
if val < 0: val = 0
|
||||
if val > 255: val = 255
|
||||
comp.plane[(by * 8 + y) * pw + bx * 8 + x] = uint8(val)
|
||||
x = x + 1
|
||||
y = y + 1
|
||||
|
||||
def _j_render() -> im.Image*:
|
||||
global _j_stage
|
||||
_j_trig()
|
||||
i: int = 0
|
||||
while i < _j_ncomp:
|
||||
comp: JComp* = &_j_comps[i]
|
||||
pw: int = comp.bw * 8
|
||||
ph: int = comp.bh * 8
|
||||
comp.plane = uint8*(stdlib.malloc(uint64(pw * ph)))
|
||||
if comp.plane is None:
|
||||
_j_stage = 8
|
||||
return None
|
||||
by: int = 0
|
||||
while by < comp.bh:
|
||||
bx: int = 0
|
||||
while bx < comp.bw:
|
||||
_j_idct_block(comp, bx, by)
|
||||
bx = bx + 1
|
||||
by = by + 1
|
||||
i = i + 1
|
||||
img: im.Image* = im.new_image(_j_w, _j_h, im.IMAGE_FMT_JPEG)
|
||||
if img is None:
|
||||
_j_stage = 9
|
||||
return None
|
||||
ci_y: int = 0
|
||||
ci_cb: int = 1
|
||||
ci_cr: int = 2
|
||||
i = 0
|
||||
while i < _j_ncomp:
|
||||
if _j_comps[i].id == 1: ci_y = i
|
||||
elif _j_comps[i].id == 2: ci_cb = i
|
||||
elif _j_comps[i].id == 3: ci_cr = i
|
||||
i = i + 1
|
||||
c0: JComp* = &_j_comps[ci_y]
|
||||
pw0: int = c0.bw * 8
|
||||
y: int = 0
|
||||
while y < _j_h:
|
||||
x: int = 0
|
||||
while x < _j_w:
|
||||
if _j_ncomp == 1:
|
||||
cy: int = (y * c0.vs) / _j_maxv
|
||||
cx: int = (x * c0.hs) / _j_maxh
|
||||
if cy >= c0.bh * 8: cy = c0.bh * 8 - 1
|
||||
if cx >= pw0: cx = pw0 - 1
|
||||
g: int = int(c0.plane[cy * pw0 + cx])
|
||||
img.pixels[y * _j_w + x] = im.make_pixel(g, g, g, 255)
|
||||
else:
|
||||
cr: JComp* = &_j_comps[ci_cr]
|
||||
cb: JComp* = &_j_comps[ci_cb]
|
||||
yv: int = int(c0.plane[((y * c0.vs) / _j_maxv) * pw0 + ((x * c0.hs) / _j_maxh)])
|
||||
crv: int = int(cr.plane[((y * cr.vs) / _j_maxv) * (cr.bw * 8) + ((x * cr.hs) / _j_maxh)])
|
||||
cbv: int = int(cb.plane[((y * cb.vs) / _j_maxv) * (cb.bw * 8) + ((x * cb.hs) / _j_maxh)])
|
||||
if _j_rgb_mode != 0:
|
||||
img.pixels[y * _j_w + x] = im.make_pixel(yv, crv, cbv, 255)
|
||||
else:
|
||||
r: int = yv + int(float(1.402) * float(crv - 128))
|
||||
g: int = yv - int(float(0.344136) * float(cbv - 128)) - int(float(0.714136) * float(crv - 128))
|
||||
b: int = yv + int(float(1.772) * float(cbv - 128))
|
||||
if r < 0: r = 0
|
||||
if r > 255: r = 255
|
||||
if g < 0: g = 0
|
||||
if g > 255: g = 255
|
||||
if b < 0: b = 0
|
||||
if b > 255: b = 255
|
||||
img.pixels[y * _j_w + x] = im.make_pixel(r, g, b, 255)
|
||||
x = x + 1
|
||||
y = y + 1
|
||||
return img
|
||||
|
||||
# --------------------------------------------------------------------- main
|
||||
def decode(src: uint8*, src_len: uint64) -> im.Image*:
|
||||
global _j_src, _j_len, _j_pos, _j_marker, _j_err, _j_w, _j_h, _j_ncomp, _j_restart, _j_rgb_mode, _j_planes_ready
|
||||
global _j_stage, _j_scan_no
|
||||
_j_stage = 0
|
||||
_j_scan_no = 0
|
||||
if src is None or src_len < 4: return None
|
||||
if int(src[0]) != 0xFF or int(src[1]) != 0xD8: return None
|
||||
_j_stage = 1
|
||||
_j_src = src
|
||||
_j_len = src_len
|
||||
_j_pos = 2
|
||||
_j_marker = -1
|
||||
_j_err = 0
|
||||
_j_w = 0
|
||||
_j_h = 0
|
||||
_j_ncomp = 0
|
||||
_j_restart = 0
|
||||
_j_rgb_mode = 0
|
||||
_j_planes_ready = 0
|
||||
i: int = 0
|
||||
while i < 4:
|
||||
_j_qt_used[i] = 0
|
||||
i = i + 1
|
||||
while True:
|
||||
m: int = _j_next_marker()
|
||||
if m < 0 or m == 0xD9: break
|
||||
if m == 0xD8: continue
|
||||
if m >= 0xD0 and m <= 0xD7: continue
|
||||
if m == 0x01: continue
|
||||
if _j_pos + 2 > _j_len: break
|
||||
seclen: int = _j_u16(_j_pos)
|
||||
if seclen < 2: break
|
||||
pend: uint64 = _j_pos + uint64(seclen)
|
||||
if pend > _j_len: break
|
||||
p: uint64 = _j_pos + 2
|
||||
if m == 0xDB:
|
||||
if _j_parse_dqt(p, pend) != 0: return None
|
||||
_j_pos = pend
|
||||
elif m == 0xC4:
|
||||
if _j_parse_dht(p, pend) != 0: return None
|
||||
_j_pos = pend
|
||||
elif m == 0xC0 or m == 0xC1:
|
||||
if _j_parse_sof(p, pend, 0) != 0: return None
|
||||
_j_pos = pend
|
||||
elif m == 0xC2:
|
||||
# Progressive JPEG (SOF2) is parsed by the coefficient machinery
|
||||
# below but successive-approximation refinement is not trusted yet:
|
||||
# reject it instead of returning a partially-wrong image.
|
||||
_j_stage = 15
|
||||
return None
|
||||
elif m == 0xDD:
|
||||
if p + 2 <= pend: _j_restart = _j_u16(p)
|
||||
_j_pos = pend
|
||||
elif m == 0xEE:
|
||||
# Adobe APP14: transform 0 means the components are RGB.
|
||||
if pend - p >= 12:
|
||||
tr: int = int(_j_src[p + 11])
|
||||
if tr == 0: _j_rgb_mode = 1
|
||||
_j_pos = pend
|
||||
elif m == 0xDA:
|
||||
if _j_parse_sos(p, pend) != 0:
|
||||
_j_stage = 12
|
||||
return None
|
||||
_j_stage = 3
|
||||
if _j_prepare() != 0:
|
||||
_j_stage = 13
|
||||
return None
|
||||
_j_stage = 4
|
||||
kind: int = 0
|
||||
if _j_ss == 0:
|
||||
if _j_ah != 0: kind = 1
|
||||
else:
|
||||
if _j_ah != 0: kind = 3
|
||||
else: kind = 2
|
||||
_j_stage = 100 + _j_scan_no * 10 + kind
|
||||
_j_pos = pend
|
||||
if _j_scan() != 0:
|
||||
return None
|
||||
_j_scan_no = _j_scan_no + 1
|
||||
_j_stage = 5
|
||||
else:
|
||||
_j_pos = pend
|
||||
if _j_w <= 0 or _j_h <= 0 or _j_ncomp < 1:
|
||||
_j_stage = 10
|
||||
return None
|
||||
if _j_ncomp == 4:
|
||||
_j_stage = 11
|
||||
return None
|
||||
_j_stage = 6
|
||||
img: im.Image* = _j_render()
|
||||
if img is None: return None
|
||||
_j_stage = 7
|
||||
return img
|
||||
@@ -0,0 +1,303 @@
|
||||
# PNG decoder (RFC 2083): all standard color types, bit depths 1/2/4/8/16,
|
||||
# plus Adam7 interlacing. Output is packed 0xAARRGGBB in `Image.pixels`.
|
||||
|
||||
import vpsdk.imagesee.inflate as inflate
|
||||
import vpsdk.imagesee.image as im
|
||||
import vpsdk.stdlib as stdlib
|
||||
import string
|
||||
from stdint import *
|
||||
|
||||
define PNG_SIG_0 = 0x89
|
||||
define PNG_SIG_1 = 0x50
|
||||
define PNG_SIG_2 = 0x4E
|
||||
define PNG_SIG_3 = 0x47
|
||||
|
||||
_png_src: uint8* = None
|
||||
_png_len: uint64 = 0
|
||||
_png_idat: uint8* = None
|
||||
_png_idat_len: uint64 = 0
|
||||
_png_w: int = 0
|
||||
_png_h: int = 0
|
||||
_png_depth: int = 0
|
||||
_png_ctype: int = 0
|
||||
_png_interlace: int = 0
|
||||
_png_channels: int = 0
|
||||
_png_raw: uint8* = None
|
||||
_png_plte: [768]uint8
|
||||
_png_plte_n: int = 0
|
||||
_png_trns: [256]uint8
|
||||
_png_trns_n: int = 0
|
||||
_png_err: int = 0
|
||||
_png_raw_off: uint64 = 0
|
||||
|
||||
# Adam7 pass origins / steps.
|
||||
_ax0: [7]int = [0, 4, 0, 2, 0, 1, 0]
|
||||
_ay0: [7]int = [0, 0, 4, 0, 2, 0, 1]
|
||||
_adx: [7]int = [8, 8, 4, 4, 2, 2, 1]
|
||||
_ady: [7]int = [8, 8, 8, 4, 4, 2, 2]
|
||||
|
||||
def _be32(d: uint8*, o: uint64) -> uint32:
|
||||
v: uint32 = (uint32(d[o]) << 24) | (uint32(d[o + 1]) << 16)
|
||||
v = v | (uint32(d[o + 2]) << 8) | uint32(d[o + 3])
|
||||
return v
|
||||
|
||||
def _sig_ok(d: uint8*) -> int:
|
||||
if d[0] != uint8(PNG_SIG_0): return 0
|
||||
if d[1] != uint8(PNG_SIG_1): return 0
|
||||
if d[2] != uint8(PNG_SIG_2): return 0
|
||||
if d[3] != uint8(PNG_SIG_3): return 0
|
||||
return 1
|
||||
|
||||
def _channels_of(ct: int) -> int:
|
||||
if ct == 0: return 1
|
||||
if ct == 2: return 3
|
||||
if ct == 3: return 1
|
||||
if ct == 4: return 2
|
||||
if ct == 6: return 4
|
||||
return 0
|
||||
|
||||
def _depth_ok(ct: int, bd: int) -> int:
|
||||
if bd != 1 and bd != 2 and bd != 4 and bd != 8 and bd != 16: return 0
|
||||
if ct == 0: return 1
|
||||
if ct == 3: return 1 if bd <= 8 else 0
|
||||
if ct == 2 or ct == 4 or ct == 6: return 1 if bd == 8 or bd == 16 else 0
|
||||
return 0
|
||||
|
||||
def _rowbytes(w: int) -> int:
|
||||
bits: int = w * _png_channels * _png_depth
|
||||
return (bits + 7) / 8
|
||||
|
||||
def _pass_w(w: int, p: int) -> int:
|
||||
x0: int = _ax0[p]
|
||||
if w <= x0: return 0
|
||||
return (w - x0 + _adx[p] - 1) / _adx[p]
|
||||
|
||||
def _pass_h(h: int, p: int) -> int:
|
||||
y0: int = _ay0[p]
|
||||
if h <= y0: return 0
|
||||
return (h - y0 + _ady[p] - 1) / _ady[p]
|
||||
|
||||
def _raw_size() -> uint64:
|
||||
if _png_interlace == 0:
|
||||
return uint64(_png_h * (1 + _rowbytes(_png_w)))
|
||||
total: uint64 = 0
|
||||
p: int = 0
|
||||
while p < 7:
|
||||
pw: int = _pass_w(_png_w, p)
|
||||
ph: int = _pass_h(_png_h, p)
|
||||
if pw > 0 and ph > 0:
|
||||
total = total + uint64(ph * (1 + _rowbytes(pw)))
|
||||
p = p + 1
|
||||
return total
|
||||
|
||||
def _sample(line: uint8*, idx: int, depth: int) -> int:
|
||||
if depth == 8:
|
||||
return int(line[idx])
|
||||
if depth == 16:
|
||||
return (int(line[idx * 2]) << 8) | int(line[idx * 2 + 1])
|
||||
bitpos: int = idx * depth
|
||||
byte: int = int(line[bitpos >> 3])
|
||||
shift: int = 8 - depth - (bitpos & 7)
|
||||
return (byte >> shift) & ((1 << depth) - 1)
|
||||
|
||||
def _paeth(a: int, b: int, c: int) -> int:
|
||||
p: int = a + b - c
|
||||
pa: int = p - a
|
||||
if pa < 0: pa = -pa
|
||||
pb: int = p - b
|
||||
if pb < 0: pb = -pb
|
||||
pc: int = p - c
|
||||
if pc < 0: pc = -pc
|
||||
if pa <= pb and pa <= pc: return a
|
||||
if pb <= pc: return b
|
||||
return c
|
||||
|
||||
def _unfilter(ft: int, cur: uint8*, prev: uint8*, n: int, bpp: int):
|
||||
i: int = 0
|
||||
while i < n:
|
||||
a: int = 0
|
||||
c: int = 0
|
||||
if i >= bpp:
|
||||
a = int(cur[i - bpp])
|
||||
c = int(prev[i - bpp])
|
||||
b: int = int(prev[i])
|
||||
x: int = int(cur[i])
|
||||
if ft == 1:
|
||||
x = x + a
|
||||
elif ft == 2:
|
||||
x = x + b
|
||||
elif ft == 3:
|
||||
x = x + ((a + b) >> 1)
|
||||
elif ft == 4:
|
||||
x = x + _paeth(a, b, c)
|
||||
cur[i] = uint8(x & 0xFF)
|
||||
i = i + 1
|
||||
|
||||
def _scale(v: int, depth: int) -> int:
|
||||
if depth == 8: return v
|
||||
if depth == 16: return v >> 8
|
||||
mx: int = (1 << depth) - 1
|
||||
return (v * 255 + (mx / 2)) / mx
|
||||
|
||||
def _pixel_argb(line: uint8*, idx: int) -> uint32:
|
||||
ct: int = _png_ctype
|
||||
bd: int = _png_depth
|
||||
if ct == 0:
|
||||
raw: int = _sample(line, idx, bd)
|
||||
g: int = _scale(raw, bd)
|
||||
a: int = 255
|
||||
if _png_trns_n >= 2:
|
||||
tv: int = (int(_png_trns[0]) << 8) | int(_png_trns[1])
|
||||
if raw == tv: a = 0
|
||||
return im.make_pixel(g, g, g, a)
|
||||
if ct == 2:
|
||||
r: int = _sample(line, idx * 3, bd)
|
||||
g: int = _sample(line, idx * 3 + 1, bd)
|
||||
b: int = _sample(line, idx * 3 + 2, bd)
|
||||
a: int = 255
|
||||
if _png_trns_n >= 6:
|
||||
tr: int = (int(_png_trns[0]) << 8) | int(_png_trns[1])
|
||||
tg: int = (int(_png_trns[2]) << 8) | int(_png_trns[3])
|
||||
tb: int = (int(_png_trns[4]) << 8) | int(_png_trns[5])
|
||||
if r == tr and g == tg and b == tb: a = 0
|
||||
return im.make_pixel(_scale(r, bd), _scale(g, bd), _scale(b, bd), a)
|
||||
if ct == 3:
|
||||
pi: int = _sample(line, idx, bd)
|
||||
if pi * 3 + 2 >= 768: return im.make_pixel(0, 0, 0, 255)
|
||||
r: int = int(_png_plte[pi * 3])
|
||||
g: int = int(_png_plte[pi * 3 + 1])
|
||||
b: int = int(_png_plte[pi * 3 + 2])
|
||||
a: int = 255
|
||||
if pi < _png_trns_n: a = int(_png_trns[pi])
|
||||
return im.make_pixel(r, g, b, a)
|
||||
if ct == 4:
|
||||
g: int = _sample(line, idx * 2, bd)
|
||||
a: int = _sample(line, idx * 2 + 1, bd)
|
||||
return im.make_pixel(_scale(g, bd), _scale(g, bd), _scale(g, bd), _scale(a, bd))
|
||||
if ct == 6:
|
||||
r: int = _sample(line, idx * 4, bd)
|
||||
g: int = _sample(line, idx * 4 + 1, bd)
|
||||
b: int = _sample(line, idx * 4 + 2, bd)
|
||||
a: int = _sample(line, idx * 4 + 3, bd)
|
||||
return im.make_pixel(_scale(r, bd), _scale(g, bd), _scale(b, bd), _scale(a, bd))
|
||||
return im.make_pixel(0, 0, 0, 255)
|
||||
|
||||
def _decode_pass(out: uint32*, p: int):
|
||||
global _png_raw_off
|
||||
pw: int = _pass_w(_png_w, p)
|
||||
ph: int = _pass_h(_png_h, p)
|
||||
px0: int = _ax0[p]
|
||||
py0: int = _ay0[p]
|
||||
pdx: int = _adx[p]
|
||||
pdy: int = _ady[p]
|
||||
if _png_interlace == 0:
|
||||
# Non-interlaced images are a single full-size pass, not Adam7 pass 0.
|
||||
pw = _png_w
|
||||
ph = _png_h
|
||||
px0 = 0
|
||||
py0 = 0
|
||||
pdx = 1
|
||||
pdy = 1
|
||||
if pw <= 0 or ph <= 0: return
|
||||
rb: int = _rowbytes(pw)
|
||||
bpp: int = (_png_channels * _png_depth + 7) / 8
|
||||
if bpp < 1: bpp = 1
|
||||
cur: uint8* = uint8*(stdlib.malloc(uint64(rb)))
|
||||
prev: uint8* = uint8*(stdlib.malloc(uint64(rb)))
|
||||
if cur is None or prev is None: return
|
||||
string.memset(rawptr(cur), 0, uint64(rb))
|
||||
string.memset(rawptr(prev), 0, uint64(rb))
|
||||
off: uint64 = _png_raw_off
|
||||
ry: int = 0
|
||||
while ry < ph:
|
||||
ft: int = int(_png_raw[off])
|
||||
off = off + 1
|
||||
string.memcpy(rawptr(cur), rawptr(uint64(_png_raw) + off), uint64(rb))
|
||||
off = off + uint64(rb)
|
||||
_unfilter(ft, cur, prev, rb, bpp)
|
||||
y: int = py0 + ry * pdy
|
||||
rx: int = 0
|
||||
while rx < pw:
|
||||
x: int = px0 + rx * pdx
|
||||
if x < _png_w and y < _png_h:
|
||||
out[y * _png_w + x] = _pixel_argb(cur, rx)
|
||||
rx = rx + 1
|
||||
string.memcpy(rawptr(prev), rawptr(cur), uint64(rb))
|
||||
ry = ry + 1
|
||||
_png_raw_off = off
|
||||
|
||||
def decode(src: uint8*, src_len: uint64) -> im.Image*:
|
||||
global _png_src, _png_len, _png_w, _png_h, _png_depth, _png_ctype
|
||||
global _png_interlace, _png_channels, _png_raw, _png_plte_n, _png_trns_n
|
||||
global _png_idat, _png_idat_len, _png_err, _png_raw_off
|
||||
if src is None or src_len < 33: return None
|
||||
if _sig_ok(src) == 0: return None
|
||||
_png_src = src
|
||||
_png_len = src_len
|
||||
_png_w = 0
|
||||
_png_h = 0
|
||||
_png_plte_n = 0
|
||||
_png_trns_n = 0
|
||||
_png_err = 0
|
||||
_png_raw_off = 0
|
||||
_png_idat = uint8*(stdlib.malloc(src_len))
|
||||
if _png_idat is None: return None
|
||||
_png_idat_len = 0
|
||||
pos: uint64 = 8
|
||||
got_ihdr: int = 0
|
||||
while pos + 12 <= src_len:
|
||||
clen: uint64 = uint64(_be32(src, pos))
|
||||
if pos + 12 + clen > src_len: return None
|
||||
typ0: int = int(src[pos + 4])
|
||||
typ1: int = int(src[pos + 5])
|
||||
typ2: int = int(src[pos + 6])
|
||||
typ3: int = int(src[pos + 7])
|
||||
dpos: uint64 = pos + 8
|
||||
if typ0 == 73 and typ1 == 72 and typ2 == 68 and typ3 == 82: # IHDR
|
||||
if clen < 13: return None
|
||||
_png_w = int(_be32(src, dpos))
|
||||
_png_h = int(_be32(src, dpos + 4))
|
||||
_png_depth = int(src[dpos + 8])
|
||||
_png_ctype = int(src[dpos + 9])
|
||||
_png_interlace = int(src[dpos + 12])
|
||||
_png_channels = _channels_of(_png_ctype)
|
||||
if _png_w <= 0 or _png_h <= 0 or _png_w > 16384 or _png_h > 16384: return None
|
||||
if _png_channels == 0 or _depth_ok(_png_ctype, _png_depth) == 0: return None
|
||||
if int(src[dpos + 10]) != 0 or int(src[dpos + 11]) != 0: return None
|
||||
got_ihdr = 1
|
||||
elif typ0 == 80 and typ1 == 76 and typ2 == 84 and typ3 == 69: # PLTE
|
||||
n: uint64 = clen
|
||||
if n > 768: n = 768
|
||||
string.memcpy(rawptr(&_png_plte[0]), rawptr(uint64(src) + dpos), n)
|
||||
_png_plte_n = int(n / 3)
|
||||
elif typ0 == 116 and typ1 == 82 and typ2 == 78 and typ3 == 83: # tRNS
|
||||
n: uint64 = clen
|
||||
if n > 256: n = 256
|
||||
string.memcpy(rawptr(&_png_trns[0]), rawptr(uint64(src) + dpos), n)
|
||||
_png_trns_n = int(n)
|
||||
elif typ0 == 73 and typ1 == 68 and typ2 == 65 and typ3 == 84: # IDAT
|
||||
string.memcpy(rawptr(uint64(_png_idat) + _png_idat_len),
|
||||
rawptr(uint64(src) + dpos), clen)
|
||||
_png_idat_len = _png_idat_len + clen
|
||||
elif typ0 == 73 and typ1 == 69 and typ2 == 78 and typ3 == 68: # IEND
|
||||
break
|
||||
pos = pos + 12 + clen
|
||||
if got_ihdr == 0 or _png_idat_len == 0: return None
|
||||
|
||||
rawsz: uint64 = _raw_size()
|
||||
if rawsz == 0: return None
|
||||
_png_raw = uint8*(stdlib.malloc(rawsz))
|
||||
if _png_raw is None: return None
|
||||
got_raw: int = inflate.zlib_decompress(_png_idat, _png_idat_len, _png_raw, rawsz)
|
||||
if got_raw < 0 or uint64(got_raw) != rawsz: return None
|
||||
|
||||
img: im.Image* = im.new_image(_png_w, _png_h, im.IMAGE_FMT_PNG)
|
||||
if img is None: return None
|
||||
if _png_interlace == 0:
|
||||
_decode_pass(img.pixels, 0)
|
||||
else:
|
||||
p: int = 0
|
||||
while p < 7:
|
||||
_decode_pass(img.pixels, p)
|
||||
p = p + 1
|
||||
return img
|
||||
+24
-6
@@ -5,12 +5,12 @@ def exit(code: int):
|
||||
syscall._syscall1(uint64(syscall.Syscall.EXIT), uint64(code))
|
||||
|
||||
def reboot():
|
||||
"""Reset the machine."""
|
||||
syscall._syscall0(uint64(syscall.Syscall.REBOOT))
|
||||
"""Reset the machine via Linux reboot(169, LINUX_REBOOT_CMD_RESTART)."""
|
||||
syscall.reboot_linux(uint64(0x01234567))
|
||||
|
||||
def shutdown():
|
||||
"""Power off the machine via ACPI S5."""
|
||||
syscall._syscall0(uint64(syscall.Syscall.SHUTDOWN))
|
||||
"""Power off the machine via Linux reboot(169, LINUX_REBOOT_CMD_POWER_OFF)."""
|
||||
syscall.reboot_linux(uint64(0x4321FEDC))
|
||||
|
||||
def thread_exit():
|
||||
"""当前用户线程显式退出:内核回收其用户栈并标记线程结束(不返回)。"""
|
||||
@@ -25,11 +25,29 @@ def get_pid() -> int:
|
||||
"""当前进程 pid(getpid)。/dev/desk 的事件按 pid 过滤,建窗前就要拿到它。"""
|
||||
return int(syscall._syscall0(uint64(syscall.Syscall.GETPID)))
|
||||
|
||||
def get_ppid() -> int:
|
||||
return int(syscall._syscall0(uint64(syscall.Syscall.GETPPID)))
|
||||
|
||||
def spawn(path: const str, blocking: int = 0) -> int:
|
||||
return int(syscall._syscall2(uint64(syscall.Syscall.SPAWN), uint64(path), uint64(blocking)))
|
||||
"""ViperOS launcher: start a new child and keep the caller alive."""
|
||||
return int(syscall._syscall2(uint64(syscall.Syscall.SPAWN_PROC), uint64(path), uint64(blocking)))
|
||||
|
||||
def execve(path: const str, argv: rawptr, envp: rawptr) -> int:
|
||||
"""Linux execve: replace the current process (returns only on failure)."""
|
||||
return int(syscall._syscall3(uint64(syscall.Syscall.SPAWN), uint64(path), uint64(argv), uint64(envp)))
|
||||
|
||||
def fork() -> int:
|
||||
return int(syscall._syscall0(uint64(syscall.Syscall.FORK)))
|
||||
|
||||
def clone(flags: uint64, stack: uint64, ptid: uint64 = 0, ctid: uint64 = 0, tls: uint64 = 0) -> int:
|
||||
return int(syscall._syscall5(uint64(syscall.Syscall.CLONE), flags, stack, ptid, ctid, tls))
|
||||
|
||||
def wait4(pid: int, status: rawptr, options: uint32 = 0) -> int:
|
||||
return int(syscall._syscall4(uint64(syscall.Syscall.WAIT4), uint64(pid),
|
||||
uint64(status), uint64(options), 0))
|
||||
|
||||
def thread_create(func: rawptr, arg: rawptr = None) -> int:
|
||||
return int(syscall._syscall2(uint64(syscall.Syscall.THREAD_CREATE), uint64(func), uint64(arg)))
|
||||
return int(syscall._syscall2(uint64(syscall.Syscall.THREAD_CREATE_OLD), uint64(func), uint64(arg)))
|
||||
|
||||
|
||||
class Thread:
|
||||
|
||||
+311
-38
@@ -1,39 +1,89 @@
|
||||
# ============================================================================
|
||||
# 系统调用号表(SDK 侧镜像)。**与 VKernel/Kernel/intr/syscall.vp 的同名 enum 逐条对应**:
|
||||
# vpsdk 不允许依赖 VKernel,所以这里是一份必须手工同步的副本 —— 增删/改号/改名两边一起改。
|
||||
# 编号口径:与 Linux x86-64 ABI 同名同号;Linux 有而我们未实现的留空不占号;
|
||||
# 我们独有的能力不再新增 syscall,最终走 devfs 设备文件或下沉到用户层(见 0x1000+ 段的标注)。
|
||||
# 系统调用号表(SDK 侧镜像,由 Scripts/syscall_table.py 生成,禁止手改)。
|
||||
# 编号口径:与 Linux x86-64 ABI 同名同号;Linux 有而尚未实现的调用暂不占号。
|
||||
# ============================================================================
|
||||
# BEGIN GENERATED SYSCALL TABLE
|
||||
enum Syscall:
|
||||
# ---- Linux 对齐段(号 = Linux x86-64 的号)----
|
||||
READ = 0 # read(fd, buf, count)
|
||||
WRITE = 1 # write(fd, buf, count)
|
||||
OPEN = 2 # open(path, flags)
|
||||
CLOSE = 3 # close(fd)
|
||||
STAT = 4 # stat(path, info)(结构体仍是本系统的 fileinfo)
|
||||
SEEK = 8 # lseek(fd, offset, whence)(whence 目前恒 0 = SEEK_SET)
|
||||
MMAP = 9 # mmap(size) → VA(ABI 待向 Linux 的 addr/len/prot/flags/fd/off 靠拢)
|
||||
MUNMAP = 11 # munmap(ptr, size)(目前空实现)
|
||||
IOCTL = 16 # ioctl(fd, request, arg):设备控制(当前用于 /dev/fb0 查几何)
|
||||
YIELD = 24 # sched_yield()
|
||||
GETPID = 39 # getpid():返回当前进程 pid(app 用它过滤 /dev/desk 事件)
|
||||
THREAD_CREATE = 56 # clone():建用户线程,返回 tid
|
||||
SPAWN = 59 # execve():加载并运行新 ELF,返回 pid
|
||||
THREAD_EXIT = 60 # exit():退役当前线程
|
||||
MKDIR = 83 # mkdir(path)
|
||||
REMOVE = 87 # unlink(path)
|
||||
EXIT = 231 # exit_group():退役当前线程(窗口由 explorer 侧回收)
|
||||
# ---- 临时私有段(0x1000+):Linux 无对应,且**逐条都会被消掉** ----
|
||||
# 目标形态:私有能力不占 syscall 号 → 改 devfs 设备文件。
|
||||
# S3e 删 UI 11 个;S4 删 SERIAL_PUTS(改 /dev/serial0);S5 删 TELL/SIZE
|
||||
# (改 lseek 的 SEEK_CUR / SEEK_END,见 vpsdk/fs.vp 的 tell/fsize)。
|
||||
OPENDIR = 0x1003 # TODO(linux):openat(O_DIRECTORY) + getdents64
|
||||
READDIR = 0x1004 # TODO(linux):getdents64
|
||||
CLOSEDIR = 0x1005 # TODO(linux):close
|
||||
LOAD_SO = 0x1006 # TODO(userspace): dynamic library loading
|
||||
SO_CALL1 = 0x1007 # TODO(userspace): dynamic library call
|
||||
REBOOT = 0x1008
|
||||
SHUTDOWN = 0x1009
|
||||
# ---- Linux 对齐段(号 = Linux x86-64 的号) ----
|
||||
READ = 0 # read(fd, buf, count)
|
||||
WRITE = 1 # write(fd, buf, count)
|
||||
OPEN = 2 # open(path, flags) - Linux O_*
|
||||
CLOSE = 3 # close(fd)
|
||||
STAT = 4 # stat(path, fileinfo*) - legacy struct
|
||||
FSTAT = 5 # fstat(fd, struct stat*)
|
||||
POLL = 7 # poll(fds, nfds, timeout) - stub 0
|
||||
SEEK = 8 # lseek(fd, offset, whence) -> new offset
|
||||
MMAP = 9 # mmap(addr, len, prot, flags, fd, off)
|
||||
MPROTECT = 10 # mprotect(addr, len, prot)
|
||||
MUNMAP = 11 # munmap(addr, len)
|
||||
BRK = 12 # brk(addr) -> new break
|
||||
RT_SIGACTION = 13 # rt_sigaction() - bookkeeping
|
||||
RT_SIGPROCMASK = 14 # rt_sigprocmask() - bookkeeping
|
||||
RT_SIGRETURN = 15 # rt_sigreturn()
|
||||
IOCTL = 16 # ioctl(fd, request, arg)
|
||||
PREAD64 = 17 # pread64(fd, buf, count, offset)
|
||||
PWRITE64 = 18 # pwrite64(fd, buf, count, offset)
|
||||
READV = 19 # readv(fd, iov, iovcnt)
|
||||
WRITEV = 20 # writev(fd, iov, iovcnt)
|
||||
ACCESS = 21 # access(path, mode)
|
||||
PIPE = 22 # pipe(int[2])
|
||||
YIELD = 24 # sched_yield()
|
||||
MREMAP = 25 # mremap(old, oldsz, newsz, flags, newaddr)
|
||||
MADVISE = 28 # madvise(addr, len, advice)
|
||||
DUP = 32 # dup(fd)
|
||||
DUP2 = 33 # dup2(oldfd, newfd)
|
||||
NANOSLEEP = 35 # nanosleep(req, rem)
|
||||
GETPID = 39 # getpid()
|
||||
CLONE = 56 # clone(flags, stack, ptid, ctid, tls)
|
||||
FORK = 57 # fork() - copy address space
|
||||
SPAWN = 59 # execve(path, argv, envp)
|
||||
THREAD_EXIT = 60 # exit(): retire current thread
|
||||
WAIT4 = 61 # wait4(pid, status, options, rusage)
|
||||
KILL = 62 # kill(pid, sig) - bookkeeping
|
||||
UNAME = 63 # uname(utsname*)
|
||||
FCNTL = 72 # fcntl(fd, cmd, arg)
|
||||
GETCWD = 79 # getcwd(buf, size)
|
||||
CHDIR = 80 # chdir(path)
|
||||
RENAME = 82 # rename(old, new)
|
||||
MKDIR = 83 # mkdir(path)
|
||||
RMDIR = 84 # rmdir(path)
|
||||
REMOVE = 87 # unlink(path)
|
||||
READLINK = 89 # readlink(path, buf, size) - no symlinks
|
||||
GETUID = 102 # getuid()
|
||||
GETGID = 104 # getgid()
|
||||
GETEUID = 107 # geteuid()
|
||||
GETEGID = 108 # getegid()
|
||||
GETTIMEOFDAY = 96 # gettimeofday(tv, tz)
|
||||
GETPPID = 110 # getppid()
|
||||
ARCH_PRCTL = 158 # arch_prctl(code, addr)
|
||||
REBOOT_LINUX = 169 # reboot(magic1, magic2, cmd, arg)
|
||||
GETTID = 186 # gettid()
|
||||
FUTEX = 202 # futex(uaddr, op, val, timeout)
|
||||
GETDENTS64 = 217 # getdents64(fd, buf, count)
|
||||
SET_TID_ADDRESS = 218 # set_tid_address(tidptr)
|
||||
CLOCK_GETTIME = 228 # clock_gettime(clockid, timespec*)
|
||||
CLOCK_GETRES = 229 # clock_getres(clockid, timespec*)
|
||||
EXIT = 231 # exit_group(): terminate all threads
|
||||
OPENAT = 257 # openat(dirfd, path, flags, mode)
|
||||
NEWFSTATAT = 262 # newfstatat(dirfd, path, stat*, flags)
|
||||
UNLINKAT = 263 # unlinkat(dirfd, path, flags)
|
||||
READLINKAT = 267 # readlinkat(dirfd, path, buf, size)
|
||||
FACCESSAT = 269 # faccessat(dirfd, path, mode, flags)
|
||||
SET_ROBUST_LIST = 273 # set_robust_list(head, len)
|
||||
DUP3 = 292 # dup3(oldfd, newfd, flags)
|
||||
PIPE2 = 293 # pipe2(int[2], flags)
|
||||
PRLIMIT64 = 302 # prlimit64(pid, resource, new, old)
|
||||
GETRANDOM = 318 # getrandom(buf, len, flags)
|
||||
STATX = 332 # statx(...) - -ENOSYS, glibc falls back
|
||||
RSEQ = 334 # rseq(rseq, len, flags) - -ENOSYS
|
||||
# ---- 临时私有段(0x1000+):Linux 无对应,逐条迁出 ----
|
||||
LOAD_SO = 0x1006 # TODO(userspace): dynamic library loading
|
||||
SO_CALL1 = 0x1007 # TODO(userspace): dynamic library call
|
||||
REBOOT = 0x1008 # TODO(linux): reboot(169)
|
||||
SHUTDOWN = 0x1009 # TODO(linux): reboot(RB_POWER_OFF)
|
||||
SPAWN_PROC = 0x100A # TODO(userspace): Terminal run helper
|
||||
THREAD_CREATE_OLD = 0x100B # TODO(userspace): ViperOS thread_create
|
||||
# END GENERATED SYSCALL TABLE
|
||||
|
||||
# ---- mmap 的 prot / flags(与内核 VKernel/intr/syscall.vp 及各 Linux 同名常量一致)----
|
||||
define PROT_NONE = 0x0
|
||||
@@ -46,6 +96,39 @@ define MAP_FIXED = 0x10
|
||||
define MAP_ANONYMOUS = 0x20
|
||||
# fd = -1(Linux 匿名映射约定)
|
||||
define MMAP_FD_ANON: uint64 = 0xFFFFFFFFFFFFFFFF
|
||||
define MAP_FAILED: uint64 = 0xFFFFFFFFFFFFFFFF
|
||||
define MREMAP_MAYMOVE = 1
|
||||
define MREMAP_FIXED = 2
|
||||
|
||||
# ---- AT_* / fcntl(Linux 值)----
|
||||
define AT_FDCWD = -100
|
||||
define AT_SYMLINK_NOFOLLOW = 256
|
||||
define AT_REMOVEDIR = 512
|
||||
define AT_EMPTY_PATH = 4096
|
||||
|
||||
define F_DUPFD = 0
|
||||
define F_GETFD = 1
|
||||
define F_SETFD = 2
|
||||
define F_GETFL = 3
|
||||
define F_SETFL = 4
|
||||
define F_DUPFD_CLOEXEC = 1030
|
||||
define FD_CLOEXEC = 1
|
||||
|
||||
# ---- TLS / futex / time / wait / reboot ----
|
||||
define ARCH_SET_GS = 0x1001
|
||||
define ARCH_SET_FS = 0x1002
|
||||
define ARCH_GET_FS = 0x1003
|
||||
define ARCH_GET_GS = 0x1004
|
||||
define FUTEX_WAIT = 0
|
||||
define FUTEX_WAKE = 1
|
||||
define FUTEX_PRIVATE_FLAG = 128
|
||||
define CLOCK_REALTIME = 0
|
||||
define CLOCK_MONOTONIC = 1
|
||||
define WNOHANG = 1
|
||||
define LINUX_REBOOT_MAGIC1 = 0xFEE1DEAD
|
||||
define LINUX_REBOOT_MAGIC2 = 672274793
|
||||
define LINUX_REBOOT_CMD_RESTART = 0x01234567
|
||||
define LINUX_REBOOT_CMD_POWER_OFF = 0x4321FEDC
|
||||
|
||||
def _syscall0(num: uint64) -> uint64:
|
||||
ret: uint64
|
||||
@@ -91,6 +174,21 @@ def _syscall3(num: uint64, a1: uint64, a2: uint64, a3: uint64) -> uint64:
|
||||
ASM_DESCR.CLOBBER_RSI, ASM_DESCR.CLOBBER_RDX])
|
||||
return ret
|
||||
|
||||
def _syscall4(num: uint64, a1: uint64, a2: uint64, a3: uint64, a4: uint64) -> uint64:
|
||||
ret: uint64
|
||||
__asm__(f"""mov rdi, {asminp(a1, ASM_DESCR.REG_ANY)}
|
||||
mov rsi, {asminp(a2, ASM_DESCR.REG_ANY)}
|
||||
mov rdx, {asminp(a3, ASM_DESCR.REG_ANY)}
|
||||
mov r10, {asminp(a4, ASM_DESCR.REG_ANY)}
|
||||
mov rax, {asminp(num, ASM_DESCR.REG_ANY)}
|
||||
syscall""",
|
||||
out=[asmout(ret, ASM_DESCR.OUTPUT_REG)],
|
||||
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RCX,
|
||||
ASM_DESCR.CLOBBER_R11, ASM_DESCR.CLOBBER_RDI,
|
||||
ASM_DESCR.CLOBBER_RSI, ASM_DESCR.CLOBBER_RDX,
|
||||
ASM_DESCR.CLOBBER_R10])
|
||||
return ret
|
||||
|
||||
def _syscall5(num: uint64, a1: uint64, a2: uint64, a3: uint64, a4: uint64, a5: uint64) -> uint64:
|
||||
ret: uint64
|
||||
__asm__(f"""mov rdi, {asminp(a1, ASM_DESCR.REG_ANY)}
|
||||
@@ -126,10 +224,185 @@ def _syscall6(num: uint64, a1: uint64, a2: uint64, a3: uint64, a4: uint64, a5: u
|
||||
return ret
|
||||
|
||||
def mmap(addr: uint64, length: uint64, prot: uint64, flags: uint64, fd: uint64, offset: uint64) -> uint64:
|
||||
"""mmap(addr, length, prot, flags, fd, offset):返回映射起始 VA,失败返回 0。
|
||||
"""mmap(addr, length, prot, flags, fd, offset):返回映射起始 VA。
|
||||
|
||||
匿名映射:fd = MMAP_FD_ANON(-1) 且 flags 带 MAP_ANONYMOUS;
|
||||
映射设备(如 /dev/fb0):fd 传该设备的 fd。addr / MAP_FIXED 由内核挑地址。
|
||||
(定义放在 _syscall6 之后,避免同模块内前向引用。)
|
||||
内核遵循 Linux 语义(失败返回 MAP_FAILED = -1);SDK 为兼容既有调用方
|
||||
把它归一化为 0(C 代码里判 `== 0`)。
|
||||
"""
|
||||
return _syscall6(uint64(Syscall.MMAP), addr, length, prot, flags, fd, offset)
|
||||
va: uint64 = _syscall6(uint64(Syscall.MMAP), addr, length, prot, flags, fd, offset)
|
||||
if va == MAP_FAILED: return uint64(0)
|
||||
return va
|
||||
|
||||
def munmap(addr: uint64, length: uint64) -> int:
|
||||
return int(_syscall2(uint64(Syscall.MUNMAP), addr, length))
|
||||
|
||||
def mprotect(addr: uint64, length: uint64, prot: uint64) -> int:
|
||||
return int(_syscall3(uint64(Syscall.MPROTECT), addr, length, prot))
|
||||
|
||||
def brk(addr: uint64) -> uint64:
|
||||
return _syscall1(uint64(Syscall.BRK), addr)
|
||||
|
||||
def mremap(old_addr: uint64, old_size: uint64, new_size: uint64, flags: uint64, new_addr: uint64 = 0) -> uint64:
|
||||
va: uint64 = _syscall5(uint64(Syscall.MREMAP), old_addr, old_size, new_size, flags, new_addr)
|
||||
if va == MAP_FAILED: return uint64(0)
|
||||
return va
|
||||
|
||||
def madvise(addr: uint64, length: uint64, advice: uint32) -> int:
|
||||
return int(_syscall3(uint64(Syscall.MADVISE), addr, length, uint64(advice)))
|
||||
|
||||
# ============================================================================
|
||||
# P0 flat wrappers (Linux ABI). Higher level helpers live in vpsdk.fs / process.
|
||||
# ============================================================================
|
||||
|
||||
def uname(buf: rawptr) -> int:
|
||||
return int(_syscall1(uint64(Syscall.UNAME), uint64(buf)))
|
||||
|
||||
def getuid() -> int:
|
||||
return int(_syscall0(uint64(Syscall.GETUID)))
|
||||
|
||||
def geteuid() -> int:
|
||||
return int(_syscall0(uint64(Syscall.GETEUID)))
|
||||
|
||||
def getgid() -> int:
|
||||
return int(_syscall0(uint64(Syscall.GETGID)))
|
||||
|
||||
def getegid() -> int:
|
||||
return int(_syscall0(uint64(Syscall.GETEGID)))
|
||||
|
||||
def getppid() -> int:
|
||||
return int(_syscall0(uint64(Syscall.GETPPID)))
|
||||
|
||||
def gettid() -> int:
|
||||
return int(_syscall0(uint64(Syscall.GETTID)))
|
||||
|
||||
def getcwd(buf: rawptr, size: uint64) -> int:
|
||||
return int(_syscall2(uint64(Syscall.GETCWD), uint64(buf), size))
|
||||
|
||||
def chdir(path: const str) -> int:
|
||||
return int(_syscall1(uint64(Syscall.CHDIR), uint64(path)))
|
||||
|
||||
def access(path: const str, mode: uint32) -> int:
|
||||
return int(_syscall2(uint64(Syscall.ACCESS), uint64(path), uint64(mode)))
|
||||
|
||||
def faccessat(dirfd: int, path: const str, mode: uint32, flags: uint32) -> int:
|
||||
return int(_syscall4(uint64(Syscall.FACCESSAT), uint64(dirfd), uint64(path),
|
||||
uint64(mode), uint64(flags)))
|
||||
|
||||
def readlink(path: const str, buf: rawptr, size: uint64) -> int:
|
||||
return int(_syscall3(uint64(Syscall.READLINK), uint64(path), uint64(buf), size))
|
||||
|
||||
def readlinkat(dirfd: int, path: const str, buf: rawptr, size: uint64) -> int:
|
||||
return int(_syscall4(uint64(Syscall.READLINKAT), uint64(dirfd), uint64(path),
|
||||
uint64(buf), size))
|
||||
|
||||
def set_tid_address(tidptr: rawptr) -> int: return int(_syscall1(uint64(Syscall.SET_TID_ADDRESS), uint64(tidptr)))
|
||||
|
||||
def set_robust_list(head: rawptr, length: uint64) -> int:
|
||||
return int(_syscall2(uint64(Syscall.SET_ROBUST_LIST), uint64(head), length))
|
||||
|
||||
def rseq(rseq_ptr: rawptr, length: uint32, flags: uint32) -> int:
|
||||
return int(_syscall3(uint64(Syscall.RSEQ), uint64(rseq_ptr),
|
||||
uint64(length), uint64(flags)))
|
||||
|
||||
def prlimit64(pid: int, resource: uint32, new_limit: rawptr, old_limit: rawptr) -> int:
|
||||
return int(_syscall4(uint64(Syscall.PRLIMIT64), uint64(pid), uint64(resource),
|
||||
uint64(new_limit), uint64(old_limit)))
|
||||
|
||||
def getrandom(buf: rawptr, length: uint64, flags: uint32) -> int:
|
||||
return int(_syscall3(uint64(Syscall.GETRANDOM), uint64(buf), length, uint64(flags)))
|
||||
|
||||
# ---------------------------------------------------------------- P1 ----
|
||||
|
||||
def openat(dirfd: int, path: const str, flags: uint32, mode: uint32 = 0) -> int:
|
||||
return int(_syscall4(uint64(Syscall.OPENAT), uint64(dirfd), uint64(path),
|
||||
uint64(flags), uint64(mode)))
|
||||
|
||||
def fstat(fd: int, st: rawptr) -> int:
|
||||
return int(_syscall2(uint64(Syscall.FSTAT), uint64(fd), uint64(st)))
|
||||
|
||||
def newfstatat(dirfd: int, path: const str, st: rawptr, flags: uint32) -> int:
|
||||
return int(_syscall4(uint64(Syscall.NEWFSTATAT), uint64(dirfd), uint64(path),
|
||||
uint64(st), uint64(flags)))
|
||||
|
||||
def getdents64(fd: int, buf: rawptr, count: uint64) -> int:
|
||||
return int(_syscall3(uint64(Syscall.GETDENTS64), uint64(fd), uint64(buf), count))
|
||||
|
||||
def dup(fd: int) -> int:
|
||||
return int(_syscall1(uint64(Syscall.DUP), uint64(fd)))
|
||||
|
||||
def dup2(oldfd: int, newfd: int) -> int:
|
||||
return int(_syscall2(uint64(Syscall.DUP2), uint64(oldfd), uint64(newfd)))
|
||||
|
||||
def dup3(oldfd: int, newfd: int, flags: uint32) -> int:
|
||||
return int(_syscall3(uint64(Syscall.DUP3), uint64(oldfd), uint64(newfd), uint64(flags)))
|
||||
|
||||
def fcntl(fd: int, cmd: uint32, arg: uint64) -> int:
|
||||
return int(_syscall3(uint64(Syscall.FCNTL), uint64(fd), uint64(cmd), arg))
|
||||
|
||||
def pread64(fd: int, buf: rawptr, count: uint32, offset: uint64) -> int:
|
||||
return int(_syscall4(uint64(Syscall.PREAD64), uint64(fd), uint64(buf),
|
||||
uint64(count), offset))
|
||||
|
||||
def pwrite64(fd: int, buf: rawptr, count: uint32, offset: uint64) -> int:
|
||||
return int(_syscall4(uint64(Syscall.PWRITE64), uint64(fd), uint64(buf),
|
||||
uint64(count), offset))
|
||||
|
||||
def rename(old_path: const str, new_path: const str) -> int:
|
||||
return int(_syscall2(uint64(Syscall.RENAME), uint64(old_path), uint64(new_path)))
|
||||
|
||||
def rmdir(path: const str) -> int:
|
||||
return int(_syscall1(uint64(Syscall.RMDIR), uint64(path)))
|
||||
|
||||
def unlinkat(dirfd: int, path: const str, flags: uint32) -> int:
|
||||
return int(_syscall3(uint64(Syscall.UNLINKAT), uint64(dirfd), uint64(path),
|
||||
uint64(flags)))
|
||||
|
||||
# ---------------------------------------------------------------- P4/P5/P6 ----
|
||||
|
||||
def arch_prctl(code: int, addr: uint64) -> int:
|
||||
return int(_syscall2(uint64(Syscall.ARCH_PRCTL), uint64(code), addr))
|
||||
|
||||
def futex(uaddr: uint64, op: uint32, val: uint32, timeout: uint64 = 0) -> int:
|
||||
return int(_syscall4(uint64(Syscall.FUTEX), uaddr, uint64(op), uint64(val), timeout))
|
||||
|
||||
def clock_gettime(clockid: int, ts: rawptr) -> int:
|
||||
return int(_syscall2(uint64(Syscall.CLOCK_GETTIME), uint64(clockid), uint64(ts)))
|
||||
|
||||
def clock_getres(clockid: int, ts: rawptr) -> int:
|
||||
return int(_syscall2(uint64(Syscall.CLOCK_GETRES), uint64(clockid), uint64(ts)))
|
||||
|
||||
def gettimeofday(tv: rawptr, tz: rawptr = None) -> int:
|
||||
return int(_syscall2(uint64(Syscall.GETTIMEOFDAY), uint64(tv), uint64(tz)))
|
||||
|
||||
def nanosleep(req: rawptr, rem: rawptr = None) -> int:
|
||||
return int(_syscall2(uint64(Syscall.NANOSLEEP), uint64(req), uint64(rem)))
|
||||
|
||||
def rt_sigaction(sig: int, act: rawptr, oldact: rawptr, sigsetsize: uint64 = 8) -> int:
|
||||
return int(_syscall4(uint64(Syscall.RT_SIGACTION), uint64(sig), uint64(act),
|
||||
uint64(oldact), sigsetsize))
|
||||
|
||||
def rt_sigprocmask(how: int, setp: rawptr, oldset: rawptr, sigsetsize: uint64 = 8) -> int:
|
||||
return int(_syscall4(uint64(Syscall.RT_SIGPROCMASK), uint64(how), uint64(setp),
|
||||
uint64(oldset), sigsetsize))
|
||||
|
||||
def kill(pid: int, sig: int) -> int:
|
||||
return int(_syscall2(uint64(Syscall.KILL), uint64(pid), uint64(sig)))
|
||||
|
||||
def readv(fd: int, iov: rawptr, iovcnt: int) -> int:
|
||||
return int(_syscall3(uint64(Syscall.READV), uint64(fd), uint64(iov), uint64(iovcnt)))
|
||||
|
||||
def writev(fd: int, iov: rawptr, iovcnt: int) -> int:
|
||||
return int(_syscall3(uint64(Syscall.WRITEV), uint64(fd), uint64(iov), uint64(iovcnt)))
|
||||
|
||||
def poll(fds: rawptr, nfds: uint64, timeout: int) -> int:
|
||||
return int(_syscall3(uint64(Syscall.POLL), uint64(fds), nfds, uint64(timeout)))
|
||||
|
||||
def pipe2(fds: rawptr, flags: uint32 = 0) -> int:
|
||||
return int(_syscall2(uint64(Syscall.PIPE2), uint64(fds), uint64(flags)))
|
||||
|
||||
def pipe(fds: rawptr) -> int:
|
||||
return int(_syscall1(uint64(Syscall.PIPE), uint64(fds)))
|
||||
|
||||
def reboot_linux(cmd: uint64) -> int:
|
||||
return int(_syscall4(uint64(Syscall.REBOOT_LINUX), uint64(LINUX_REBOOT_MAGIC1),
|
||||
uint64(LINUX_REBOOT_MAGIC2), cmd, uint64(0)))
|
||||
+2099
-2074
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user