diff --git a/.cargo/config.toml b/.cargo/config.toml index 15efea34..3cf29a6f 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,7 +7,7 @@ rthumbv7em = "run --release --target=thumbv7em-none-eabi --example" rtv7em = "rthumbv7em" # Common settings for all embedded targets -[target.'cfg(any(target_arch = "arm", target_arch = "riscv32"))'] +[target.'cfg(any(target_arch = "arm", target_arch = "riscv32", target_arch = "x86"))'] rustflags = [ "-C", "relocation-model=static", "-C", "link-arg=-icf=all", diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index f6f4d69f..0eee4e53 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -30,3 +30,5 @@ pub struct TockSyscalls; mod syscalls_impl_arm; #[cfg(target_arch = "riscv32")] mod syscalls_impl_riscv; +#[cfg(target_arch = "x86")] +mod syscalls_impl_x86; \ No newline at end of file diff --git a/runtime/src/startup/asm_x86.s b/runtime/src/startup/asm_x86.s new file mode 100644 index 00000000..96eb9cc4 --- /dev/null +++ b/runtime/src/startup/asm_x86.s @@ -0,0 +1,120 @@ +/* rt_header is defined by the general linker script (libtock_layout.ld). It has + * the following layout: + * + * Field | Offset + * ------------------------------------ + * Address of the start symbol | 0 + * Initial process break | 4 + * Top of the stack | 8 + * Size of .data | 12 + * Start of .data in flash | 16 + * Start of .data in ram | 20 + * Size of .bss | 24 + * Start of .bss in ram | 28 + */ + +/* start is the entry point -- the first code executed by the kernel. The kernel + * passes the following arguments onto the stack: + * + * esp+4 Pointer to beginning of the process binary's code. The linker script + * locates rt_header at this address. + * + * +8 Address of the beginning of the process's usable memory region. + * +12 Size of the process' allocated memory region (including grant region) + * +16 Process break provided by the kernel. + * + * We currently only use the value in esp+4. + */ + +/* int 0x03 is used to trigger a breakpoint which is promoted to a hard fault in the + absence of a debugger. This is useful to fault at failure cases where there is no + recovery path. + */ + +/* Specify that the start code is allocated and executable (ax), + * and that it should be placed in the .start section of the binary. + */ + +.section .start, "ax" +.globl start +start: + /* + * Verify that the binary was loaded to the correct + * address. We can do this by using the call command + * and grabbing the EIP off of the stack. The eip + * will be the "length of the call instruction" (5 bytes) + * ahead of the actual start of the program. + */ + + call .Lget_eip // 1 byte for the opcode + 4 bytes for the relative offset + // = 5 byte long instruction +.Lget_eip: + popl %eax // eax = eip + subl $5, %eax // eax = eip - 5 byte instruction + movl 4(%esp), %ebx // ebx = rt_header (top of memory) + movl 0(%ebx), %ecx // ecx = rt_header.start + cmpl %ecx, %eax + je .Lset_brk + /* If the binary is not at the correct location, report the error via LowLevelDebug + * then exit. */ + pushl %eax // eip, not consumed by the syscall, but is seen in trace + pushl $2 // Code 0x02 (app was not installed in the correct location) + pushl $1 // Minor number: Alert code + pushl $8 // Major number: LowLevelDebug driver + mov $2, %eax // Command syscall + int $0x40 + addl $16, %esp + pushl $0 + pushl $0 + pushl $1 // Completion code: FAIL + pushl $0 // exit-terminate + mov $6, %eax // Exit syscall + int $0x40 + addl $16, %esp + int $0x03 // If we return, trigger a fault + + + /* Set brk to rt_header initial break value */ +.Lset_brk: + movl 4(%ebx), %ecx // ecx = initial process break + pushl $0 + pushl $0 + pushl %ecx // push initial process break + pushl $0 + movl $5, %eax // memop + int $0x40 + + /* Set the stack pointer */ + mov 8(%ebx), %esp + +.Lzero_bss: + /* Zero out .bss */ + movl 24(%ebx), %ecx // ecx = remaining = rt_header.bss_size + cmpl $0, %ecx + je .Lcopy_data // If there is no .bss, jump to copying .data + movl 28(%ebx), %edi // edi = dst = rt_header.bss_start + shrl $2, %ecx // ecx = remaining / 4 = number of words to zero + cld // Clear the direction flag + xorl %eax, %eax // eax = 0, value to set .bss to + rep stosl // Zero out the .bss_size + movl 24(%ebx), %ecx // ecx = remaining = rt_header.bss_size + andl $3, %ecx // ecx = remaining % 4 = number of bytes to zero + rep stosb // Zero out the remaining bytes + +.Lcopy_data: + /* Copy .data into place */ + movl 12(%ebx), %ecx // ecx = rt_header.data_size + cmpl $0, %ecx + je .Lcall_rust_start + movl 16(%ebx), %esi // esi = src = rt_header.data_flash_start + movl 20(%ebx), %edi // edi = dst = rt_header.data_ram_start + shrl $2, %ecx // ecx = rt_header.data_size / 4 = number of words to copy + cld // Clear the direction flag + rep movsl // Copy data from flash to ram + movl 12(%ebx), %ecx // ecx = rt_header.data_size + andl $3, %ecx // ecx = rt_header.data_size % 4 = number of bytes to copy + rep movsb // Copy the remaining bytes + +.Lcall_rust_start: + jmp rust_start + int $0x03 // If we return, trigger a fault \ No newline at end of file diff --git a/runtime/src/startup/mod.rs b/runtime/src/startup/mod.rs index db2493a4..9f993e4a 100644 --- a/runtime/src/startup/mod.rs +++ b/runtime/src/startup/mod.rs @@ -9,6 +9,8 @@ use libtock_platform::{Syscalls, Termination}; core::arch::global_asm!(include_str!("asm_arm.s")); #[cfg(target_arch = "riscv32")] core::arch::global_asm!(include_str!("asm_riscv32.s")); +#[cfg(target_arch = "x86")] +core::arch::global_asm!(include_str!("asm_x86.s"), options(att_syntax)); /// `set_main!` is used to tell `libtock_runtime` where the process binary's /// `main` function is. The process binary's `main` function must have the diff --git a/runtime/src/syscalls_impl_x86.rs b/runtime/src/syscalls_impl_x86.rs new file mode 100644 index 00000000..d7e1ee42 --- /dev/null +++ b/runtime/src/syscalls_impl_x86.rs @@ -0,0 +1,150 @@ +use core::arch::asm; +use libtock_platform::{syscall_class, RawSyscalls, Register}; + +unsafe impl RawSyscalls for crate::TockSyscalls { + // Yield 1 is used for yield_wait + unsafe fn yield1([Register(r0)]: [Register; 1]) { + unsafe { + asm!( + "pushl $0", + "pushl $0", + "pushl $0", + "pushl {0}", // r0 + "movl $0, %eax", + "int $0x40", + "addl $16, %esp", + + in(reg) r0, + + // The following registers are clobbered by the syscall + out("eax") _, + out("ecx") _, + out("edx") _, + options(att_syntax), + ); + } + } + + // Yield 2 is used for yield_no_wait + unsafe fn yield2([Register(r0), Register(r1)]: [Register; 2]) { + unsafe { + asm!( + "pushl $0", + "pushl $0", + "pushl {0}", // r1 + "pushl {1}", // r0 + "movl $0, %eax", + "int $0x40", + "addl $16, %esp", + + in(reg) r1, + in(reg) r0, + + // The following registers are clobbered by the syscall + out("eax") _, + out("ecx") _, + out("edx") _, + options(att_syntax) + ); + } + } + + unsafe fn syscall1([Register(mut r0)]: [Register; 1]) -> [Register; 2] { + // This is memop, the only syscall class that syscall1 supports + let r1; + unsafe { + asm!( + "push $0", + "push $0", + "push $0", + "push {0}", // r0 + "movl $5, %eax", + "int $0x40", + "popl {0:e}", // r1 + "popl {1:e}", // r0 + "addl $8, %esp", + + inlateout(reg) r0, + out(reg) r1, + + // The following registers are clobbered by the syscall + out("eax") _, + options(att_syntax), + ); + } + [Register(r0), Register(r1)] + } + + unsafe fn syscall2( + [Register(mut r0), Register(mut r1)]: [Register; 2], + ) -> [Register; 2] { + let cmd: u32 = match CLASS { + syscall_class::MEMOP => 5, + syscall_class::EXIT => 6, + _ => unreachable!(), + }; + + unsafe { + asm!( + "pushl $0", + "pushl $0", + "pushl {0}", // r1 + "pushl {1}", // r0 + "movl {2}, %eax", // cmd + "int $0x40", + "popl {1:e}", // r0 + "popl {0:e}", // r1 + "addl $8, %esp", + + inlateout(reg) r1, + inlateout(reg) r0, + in(reg) cmd, + + // The following registers are clobbered by the syscall + out("eax") _, + options(att_syntax), + ); + } + + [Register(r0), Register(r1)] + } + + unsafe fn syscall4( + [Register(mut r0), Register(mut r1), Register(mut r2), Register(mut r3)]: [Register; 4], + ) -> [Register; 4] { + let cmd: u32 = match CLASS { + syscall_class::SUBSCRIBE => 1, + syscall_class::COMMAND => 2, + syscall_class::ALLOW_RW => 3, + syscall_class::ALLOW_RO => 4, + _ => unreachable!(), + }; + unsafe { + asm!( + "pushl {3}", // r3 + "pushl {2}", // r2 + "pushl {1}", // r1 + "pushl {0}", // r0 + "movl {4:e}, %eax", + "int $0x40", + "popl {0:e}", // r0 + "popl {1:e}", // r1 + "popl {2:e}", // r2 + "popl {3:e}", // r3 + + inlateout(reg) r0, + inlateout(reg) r1, + inlateout(reg) r2, + inlateout(reg) r3, + + in(reg) cmd, + + // The following registers are clobbered by the syscall + out("eax") _, + options(att_syntax), + ); + } + + [Register(r0), Register(r1), Register(r2), Register(r3)] + } +}