Join us on Discord!
You can help CodeWalrus stay online by donating here.

proof-of-concept kernel for the TI nspire CX

Started by nspiredev500, February 27, 2020, 09:54:34 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

lights0123

Quote from: nspiredev500 on June 10, 2020, 01:58:34 PM
Quote from: lights0123 on June 09, 2020, 01:45:42 AMI've written Rust bindings to ndless
That's cool, I thought about using Rust myself, and I even saw your project on github. My first memory manager didn't work, and I thought Rust could help with that. I ended up just doing a rewrite and it seems to work now, but for other things Rust could still be helpful. But I don't know enough about Cargo to do what I need. Maybe you can help me. I would need to compile the code like with GCC's -fPIC or -fPIE flag. It has to use the global offset table and link into my ELF file.
The global offset table is important, because it self-relocates to a higher base address, and will be mapped like this into every address space created.
I don't know too much about linking, but I'd be glad to help. rustc does compile with PIC by default:
Quoterelocation-model=[pic,static,dynamic-no-pic]
    The relocation model to use. (Default: pic)
Although apparently you can pass arbitrary arguments to LLVM too.
You'd just need to add
[lib]
crate-type = ["staticlib"]
to your Cargo.toml and make sure that you have a lib.rs instead of a main.rs source file. Building will automatically produce a libyourcratename.a that you can link to.
Quote from: nspiredev500 on June 10, 2020, 01:58:34 PMI looked at your project now, and it looks great :thumbsup: . In your book you say "Your program will never segfault". Something like this was one of the motivations for my project.
Creating a user-mode wrapper around the OS and the hardware, so no program can crash or brick your calculator.
What timer do you use for your wrapper? I want to remain compatible to as many community projects as possible.
I use the first timer for my millisecond UNIX time, because the OS doesn't use it. If your Rust wrapper also uses the first timer, both cannot work at the same time.
But I can expose timer functions as syscalls, and detecting OSExt is also easy, so you could just do a runtime check.
I use the same timer as SDL, which is at 0x900C0000. I still use that timer for sleeping, as I get more precision that the millisecond-accurate msleep Ndless function. I don't ever use the timer at 0x900D0000, although I guess I could switch to it as I don't call Ndless's sleeping functions.
I could switch to your timer syscalls if you provide a way to detect it. I'd still like to get the raw ticks if possible, for the nice 30 μs precision. I don't know how much overhead there is in a syscall—would it be worth it replicating vsyscall or vDSO since you apparently already have separate userspace memory?
Quote from: nspiredev500 on June 10, 2020, 01:58:34 PM
Quote from: lights0123 on June 09, 2020, 01:45:42 AMthis is done with fcntl and O_NONBLOCK
Nucleus writes "initializing POSIX layer" on the uart sometime during boot I think, so it should have the fcntl syscall.
The problem is to know which system call number it has and what arguments it takes.
You could assume normal linux arguments and just fuzz all the system calls in firebird until it works.
Any suggestions on how to do that?

nspiredev500

#16
Quote from: lights0123 on June 10, 2020, 03:40:02 PM
Quote from: nspiredev500 on June 10, 2020, 01:58:34 PMNucleus writes "initializing POSIX layer" on the uart sometime during boot I think, so it should have the fcntl syscall.
The problem is to know which system call number it has and what arguments it takes.
You could assume normal linux arguments and just fuzz all the system calls in firebird until it works.
Any suggestions on how to do that?
You could compile a program that tries to call each syscall number with the specified arguments, and check if it crashes.
I don't know if there is a nspire emulator that can be controlled via console options and gives the UART output on the console, but then it could be optimised.
Here is a list of emulators. You could make a script that compiles the program with a syscall number, opens the emulator, checks the output and builds the program with the next system call number.
This is basically the last option you could use if you absolutely need a specific system call.
I think you could better make small blocking I/O calls and check the time between them.
Quote from: lights0123 on June 10, 2020, 03:40:02 PMI could switch to your timer syscalls if you provide a way to detect it. I'd still like to get the raw ticks if possible, for the nice 30 μs precision. I don't know how much overhead there is in a syscall--would it be worth it replicating vsyscall or vDSO since you apparently already have separate userspace memory?
System calls are basically just C functions called by an index in a table. You just have some memory accesses to get the number and save some state, run the function, and then return. There should not be much overhead.
I technically have userspace memory, but nSLD and libndless are hardwired for supervisor mode, as the whole OS runs like that.
Before proper userspace I have to implement at least a simple scheduler, mmap and I/O syscalls. Running Rust (which should already be pretty safe) in usermode isn't worth the overhead and modifications to the libraries.
Detecting OSExt is easy, I use this in OSExt itself to detect if another version is already installed:
register uint32_t control_reg asm("r0");
asm volatile("mrc p15, 0, r0, c1, c0, 0":"=r" (control_reg)::); // reading co-processor register 1
if ((control_reg & (1 << 13)) == (1 << 13)) // if bit 13 is a 1, interrupt vectors are mapped high. I don't think anything other than OSExt does this
{
 register uint32_t tt_base asm("r0");
 asm volatile("mrc p15, 0, r0, c2, c0, 0":"=r" (tt_base)); // get the translation table base register
 
 tt_base = tt_base & (~ 0x3ff); // discard the first 14 bits, because they don't matter
 uint32_t *tt = (uint32_t*) tt_base;
 if (tt[(0xe0000000) >> 20] != 0) // if there is something mapped at virtual address 0xe0000000 it should be OSExt, I haven't seen any other program map memory like that
 {
 // OSExt is installed
 }
}
It should be very unlikely that anything else maps the exception vectors high and maps something at 0xe0000000, so this should be reliable.
You can make a variable like bool osext_installed and only do the check once.

I already have a UNIX millisecond function to get the time. I use int64_t for my millisecond time.
I can also use the watchdog timer for sleeping, it has a frequency of 33 MHZ, if you need more precision.
I could write functions to handle sleeping for longer times than the timer.
So you only need getting the time and sleeping?

By the way, in your timer code I noticed the threads only support sleeping, because threads aren't supported by ndless.
I already thought about kernel threads and a separate kernel scheduler.
If you want I can work on thread syscalls.

EDIT: I now have working microsleep and microsecond unix time.
You can specify what functionality you need and I can wrap it in a syscall.
Do you know how to call specific syscalls or do you use the wrappers ndless provides?
I know how to do it in C with inline assembly. It seems Rust supports GCC-style inline assembly, so that's good.
Is there a way to formally specify and API?
Maybe we should move this to a github issue or a separate thread.
  • Calculators owned: TI-Nspire CX CAS

nspiredev500

Just a small Hotfix release, now OSExt works with the Ndless feature for running programs from HW<W on HW-W+.
I forgot about that, and it crashed when trying to run these programs when OSExt is installed.
  • Calculators owned: TI-Nspire CX CAS

Powered by EzPortal