1#![no_std]
2mod heap;
9mod tangram;
10
11extern crate alloc;
12
13use alloc::alloc::alloc;
14use core::alloc::Layout;
15use core::sync::atomic::{AtomicUsize, Ordering};
16use tg_console::log;
17
18pub use tg_console::{print, println};
19pub use tg_syscall::*;
20
21const SYSCALL_FRAMEBUFFER: usize = 0x1000_0001;
22const SYSCALL_FRAMEBUFFER_FLUSH: usize = 0x1000_0002;
23const TG_ALLOC_ARENA_SIZE: usize = 16 << 20;
24
25#[repr(align(16))]
26struct TgAllocArena([u8; TG_ALLOC_ARENA_SIZE]);
27
28static mut TG_ALLOC_ARENA: TgAllocArena = TgAllocArena([0; TG_ALLOC_ARENA_SIZE]);
29static TG_ALLOC_OFFSET: AtomicUsize = AtomicUsize::new(0);
30
31#[unsafe(no_mangle)]
32#[unsafe(link_section = ".text.entry")]
33pub extern "C" fn _start() -> ! {
34 tg_console::init_console(&Console);
36 tg_console::set_log_level(option_env!("LOG"));
37 heap::init();
38
39 unsafe extern "C" {
40 fn main() -> i32;
41 }
42
43 exit(unsafe { main() });
45 unreachable!()
46}
47
48#[panic_handler]
49fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
50 let err = panic_info.message();
51 if let Some(location) = panic_info.location() {
52 log::error!("Panicked at {}:{}, {err}", location.file(), location.line());
53 } else {
54 log::error!("Panicked: {err}");
55 }
56 exit(1);
57 unreachable!()
58}
59
60pub fn getchar() -> u8 {
61 getchar_blocking()
62}
63
64#[cfg(target_arch = "riscv64")]
65pub fn framebuffer_info() -> Option<(*mut u8, usize, usize, usize)> {
66 let fb_ptr: isize;
67 let fb_len: usize;
68 let width: usize;
69 let height: usize;
70 unsafe {
71 core::arch::asm!(
72 "ecall",
73 inlateout("a0") 0isize => fb_ptr,
74 lateout("a1") fb_len,
75 lateout("a2") width,
76 lateout("a3") height,
77 in("a7") SYSCALL_FRAMEBUFFER,
78 );
79 }
80 if fb_ptr <= 0 || fb_len == 0 || width == 0 || height == 0 {
81 None
82 } else {
83 Some((fb_ptr as *mut u8, fb_len, width, height))
84 }
85}
86
87#[cfg(not(target_arch = "riscv64"))]
88pub fn framebuffer_info() -> Option<(*mut u8, usize, usize, usize)> {
89 None
90}
91
92#[cfg(target_arch = "riscv64")]
93pub fn framebuffer_flush() -> isize {
94 let ret: isize;
95 unsafe {
96 core::arch::asm!(
97 "ecall",
98 inlateout("a0") 0isize => ret,
99 in("a7") SYSCALL_FRAMEBUFFER_FLUSH,
100 );
101 }
102 ret
103}
104
105#[cfg(not(target_arch = "riscv64"))]
106pub fn framebuffer_flush() -> isize {
107 -1
108}
109
110pub fn render_block(block: usize) -> isize {
111 let Some((fb_ptr, fb_len, width, height)) = framebuffer_info() else {
112 return -1;
113 };
114
115 let used_len = width
116 .checked_mul(height)
117 .and_then(|pixels| pixels.checked_mul(4))
118 .unwrap_or(0);
119 if used_len == 0 || used_len > fb_len {
120 return -1;
121 }
122
123 let framebuffer = unsafe { core::slice::from_raw_parts_mut(fb_ptr, used_len) };
124 tangram::render_block_by_index(framebuffer, width, height, block);
125 framebuffer_flush()
126}
127
128pub fn getchar_poll() -> Option<u8> {
129 let mut c = [0u8; 1];
130 match read(STDIN, &mut c) {
131 1 => Some(c[0]),
132 _ => None,
133 }
134}
135
136pub fn getchar_blocking() -> u8 {
137 loop {
138 if let Some(c) = getchar_poll() {
139 return c;
140 }
141 sched_yield();
142 }
143}
144
145struct Console;
146
147impl tg_console::Console for Console {
148 #[inline]
149 fn put_char(&self, c: u8) {
150 tg_syscall::write(STDOUT, &[c]);
151 }
152
153 #[inline]
154 fn put_str(&self, s: &str) {
155 tg_syscall::write(STDOUT, s.as_bytes());
156 }
157}
158
159pub fn sleep(period_ms: usize) {
160 let mut time: TimeSpec = TimeSpec::ZERO;
162 clock_gettime(ClockId::CLOCK_MONOTONIC, &mut time as *mut _ as _);
163 let time = time + TimeSpec::from_millsecond(period_ms);
164 loop {
165 let mut now: TimeSpec = TimeSpec::ZERO;
166 clock_gettime(ClockId::CLOCK_MONOTONIC, &mut now as *mut _ as _);
167 if now > time {
168 break;
169 }
170 sched_yield();
171 }
172}
173
174pub fn get_time() -> isize {
175 let mut time: TimeSpec = TimeSpec::ZERO;
176 clock_gettime(ClockId::CLOCK_MONOTONIC, &mut time as *mut _ as _);
177 (time.tv_sec * 1000 + time.tv_nsec / 1_000_000) as isize
178}
179
180#[unsafe(no_mangle)]
181pub extern "C" fn tg_sys_open(path: *const u8, flags: i32) -> i32 {
182 if path.is_null() {
183 return -1;
184 }
185 let mut len = 0usize;
186 unsafe {
187 while *path.add(len) != 0 {
188 len += 1;
189 }
190 }
191 let raw = unsafe { core::slice::from_raw_parts(path, len) };
192 let mut start = 0usize;
193
194 while start + 1 < raw.len() && raw[start] == b'.' && raw[start + 1] == b'/' {
195 start += 2;
196 }
197 while start < raw.len() && (raw[start] == b'/' || raw[start] == b'\\') {
198 start += 1;
199 }
200
201 let mut path_bytes = &raw[start..];
202 if let Some(pos) = path_bytes.iter().rposition(|&b| b == b'/' || b == b'\\') {
203 path_bytes = &path_bytes[pos + 1..];
204 }
205 if path_bytes.is_empty() {
206 return -1;
207 }
208
209 let path_str = unsafe { core::str::from_utf8_unchecked(path_bytes) };
210 open(path_str, OpenFlags::from_bits(flags as u32).unwrap_or(OpenFlags::RDONLY)) as i32
211}
212
213#[unsafe(no_mangle)]
214pub extern "C" fn tg_sys_close(fd: i32) -> i32 {
215 if fd < 0 {
216 return -1;
217 }
218 close(fd as usize) as i32
219}
220
221#[unsafe(no_mangle)]
222pub extern "C" fn tg_sys_read(fd: i32, buf: *mut u8, len: usize) -> i32 {
223 if fd < 0 || buf.is_null() {
224 return -1;
225 }
226 let data = unsafe { core::slice::from_raw_parts_mut(buf, len) };
227 read(fd as usize, data) as i32
228}
229
230#[unsafe(no_mangle)]
231pub extern "C" fn tg_sys_write(fd: i32, buf: *const u8, len: usize) -> i32 {
232
233 if fd < 0 || buf.is_null() {
234 return -1;
235 }
236 let data = unsafe { core::slice::from_raw_parts(buf, len) };
237 write(fd as usize, data) as i32
238}
239
240#[unsafe(no_mangle)]
241pub extern "C" fn tg_sys_unlink(path: *const u8) -> i32 {
242 if path.is_null() {
243 return -1;
244 }
245 let mut len = 0usize;
246 unsafe {
247 while *path.add(len) != 0 {
248 len += 1;
249 }
250 }
251 let raw = unsafe { core::slice::from_raw_parts(path, len) };
252 let mut start = 0usize;
253
254 while start + 1 < raw.len() && raw[start] == b'.' && raw[start + 1] == b'/' {
255 start += 2;
256 }
257 while start < raw.len() && (raw[start] == b'/' || raw[start] == b'\\') {
258 start += 1;
259 }
260
261 let mut path_bytes = &raw[start..];
262 if let Some(pos) = path_bytes.iter().rposition(|&b| b == b'/' || b == b'\\') {
263 path_bytes = &path_bytes[pos + 1..];
264 }
265 if path_bytes.is_empty() {
266 return -1;
267 }
268
269 let path_str = unsafe { core::str::from_utf8_unchecked(path_bytes) };
270 unlink(path_str) as i32
271}
272
273#[unsafe(no_mangle)]
274pub extern "C" fn tg_alloc(size: usize) -> *mut u8 {
275 let req = size.max(1);
276 let align_mask = 8usize - 1;
277
278 loop {
279 let current = TG_ALLOC_OFFSET.load(Ordering::Relaxed);
280 let aligned = (current + align_mask) & !align_mask;
281 let Some(next) = aligned.checked_add(req) else {
282 return core::ptr::null_mut();
283 };
284 if next > TG_ALLOC_ARENA_SIZE {
285 return core::ptr::null_mut();
286 }
287
288 if TG_ALLOC_OFFSET
289 .compare_exchange(current, next, Ordering::SeqCst, Ordering::Relaxed)
290 .is_ok()
291 {
292 let base = core::ptr::addr_of_mut!(TG_ALLOC_ARENA) as *mut u8;
293 unsafe {
294 return base.add(aligned);
295 }
296 }
297 }
298}
299
300pub fn trace_read(ptr: *const u8) -> Option<u8> {
301 let ret = trace(0, ptr as usize, 0);
302 if ret >= 0 && ret <= 255 {
303 Some(ret as u8)
304 } else {
305 None
306 }
307}
308
309pub fn trace_write(ptr: *const u8, value: u8) -> isize {
310 trace(1, ptr as usize, value as usize)
311}
312
313pub fn count_syscall(syscall_id: usize) -> isize {
314 trace(2, syscall_id, 0)
315}
316
317pub fn pipe_read(pipe_fd: usize, buffer: &mut [u8]) -> isize {
320 let mut total_read = 0usize;
321 let len = buffer.len();
322 loop {
323 if total_read >= len {
324 return total_read as isize;
325 }
326 let ret = read(pipe_fd, &mut buffer[total_read..]);
327 if ret == -2 {
328 sched_yield();
330 continue;
331 } else if ret == 0 {
332 return total_read as isize;
334 } else if ret < 0 {
335 return ret;
337 } else {
338 total_read += ret as usize;
339 }
340 }
341}
342
343pub fn pipe_write(pipe_fd: usize, buffer: &[u8]) -> isize {
346 let mut total_write = 0usize;
347 let len = buffer.len();
348 loop {
349 if total_write >= len {
350 return total_write as isize;
351 }
352 let ret = write(pipe_fd, &buffer[total_write..]);
353 if ret == -2 {
354 sched_yield();
356 continue;
357 } else if ret < 0 {
358 return ret;
360 } else {
361 total_write += ret as usize;
362 }
363 }
364}