-
Notifications
You must be signed in to change notification settings - Fork 5
Support GOARCH=mips64 #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Hello Timur! Sorry for the long time without any response from me. I was involved in a completely different project and now returned to Embedded Go. I'm excited that someone has come that far. I need some time to review you code. Fill free to ask here any questions related to your work. |
src/runtime/cgo_noos.go
Outdated
const iscgo = false | ||
var iscgo bool | ||
|
||
func init() { iscgo = false } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function does nothing :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cgo is a delicate topic in context of small architectures. Making iscgo variable instead of a constant may increase the runtime significantly which will affect almost all MCU targets.
Hi Michal! Thanks for taking a look at this! Still working on this. It's usable but needs cleanup. Some code is N64 specific. But you probably already noticed, that most of this is from your riscv implementation. Hopefully I get some time to spend in this in the holidays. In general, would you want to merge mips support? |
I'd love to see nnos/mips64 in Embedded Go. Maybe I would wonder if we already had support for many different architectures but at the current stage of this project, a third one, although a bit exotic, should probably improve many things as the noos/riscv64 did. I've just skimmed through your commits, so I don't have the full picture. I've just learned a bit about Nintendo 64 architecture. It's a bit ancient and you probably need to extend the GOMIPS environment variable to support it without sacrifice the linux/mips64 target. One of the main assumptions is that the embedded branch should work as much well as the genuine Go for any supported GOOS/GOARCH so it must pass all tests on Linux running on qemu-mips. Do you have any experiences with the currently supported MCUs and development boards? I mean the ability to test changes made to the common code. |
Well, I fell down the N64 rabbit hole! Just bought the 1996 console, with the expansion pak and one original game. Plus, the Super 64 cartridge with the compact flash full of games headed my way... |
😆 It's a rabbit hole indeed. Not sure if you can run ROMs generated from this branch without modifications. I had some code that was specific to the EverDrive64 USB, but now it should correctly probe for available hardware. Let me know if you want help in generating or running ROMs. I recommend Ares emulator for testing and also the N64brew Discord is very active. Regarding the other supported MCUs; I don't have any of these but might just get a kendryte for testing common code. I have some general feedback and recommendations that I would like to bring up. |
For now, I'm just studying the N64 architecture and some concepts related to it, communication over pad ports, etc. When the Super 64 cartridge arrives, I will be able to run n64 / z64 binaries. I plan to disassemble it and check if there are any remnants of the USB port that the ED64 PLUS had (though the FT245R was unpopulated). What surprised me is the lack of any game that uses a light gun. I'm curious if the architecture allows to write a code that will support the old-style light guns, based on the CRT vertical and horizontal refresh. It would be great to make a light-gun hardware (or adapt an easily accessible one) and write a simple light-gun game, especially in Go :) The initial challenge may be to implement a simple light pen hardware (fotodiode/fototransistor and simple circuit that generates proper pulses over the pad port) and try to detect its position on the screen. I'm curious if this is possible. If I may suggest an MCU for testing the code, the Teensy 4.1 will be the best one. I'm slowly adding support for more it's peripherals. For now GPIO, DMA, USB and UART are ready to use. And now I'm working on SPI. By the way, when SPI will be ready, the Teensy + FTDI EVE display combo will be similar in some way to the N64 arch (separate display processor, display lists, etc.) Kendryte is a bit of an exotic board, although it has its advantages, it also has many drawbacks, especially when it comes to documentation and debugging capabilities (maybe something has improved recently). |
What I can try to do for your n64 port is to try to add support for GOOUT=n64 / z64 to the emgo tool. Just need to learn something about these formats. The last version of emgo got native support for bin and hex without using objdump. |
This reverts commit 24f83ed.
This reverts commit 83c4e53.
This reverts commit 68fea52.
This reverts commit 918d4d4.
Saving, restoring context and switching to the ISR goroutine (cpu0.gh) seems to work so far. Only syscalls and interrupts are handled so far. See TODOs for next steps.
The COMPARE register was only used because of a bug in MAME which doesn't exist anymore in newer versions. Moreover this implementation could result in a deadlock because the scheduler also sets this register.
Don't call the scheduler and enable exceptions in the user handler, it might call the scheduler.
This commit breaks external interrupt handling. Will be fixed in the next one.
This mainly prevented to print panics before the systimer was set up. See the usleep() calls in freezetheworld(). This comes with the possibility to have a non-monotonic jump in time when the systimer is finally set. Systimer implementations must respect the dummyNanoseconds if there is a risk.
Before clearing the memory via uncached access, all cache must be invalidated before. Otherwise a cache writeback could happen after clearing the memory.
The implementation of softwareInterruptHandler can't support nesting, as it must call the scheduler.
I rebased this on the wip branch for reviewing, but I think we should wait until wip is merged. I will then rebase again for the merge. What I have currently on my mind:
|
I've finished preparing master-embedded and release-branch.go1.22-embedded two days ago. For now wip and 1.22-embedded are identical, that is Wip is a good place for testing and reviewing things so for now i'll merge it here and will try use it to play with my N64.
I'm very busy until Sunday but I'll try to look at it in a free time, probably not until Sunday.
I don't known anything about the MIPS interrupts. Is there any standard interrupt controller for it or the N64 specific one? I'll tell you what it's like with the RISCV+CLINT+PLIC combo because its probably most similar to MIPS. In contrast to the ARMv7-M, the RISCV+CLINT and probably MIPS too enters the handler with interrupts disabled. There are some default priorities in the CLINT but they are relevant only when two or more interrupts/exceptions will occur at exactly the same time. So at the beginning of the main handler your goal is to as fast as possible enable interrupts again to minimize the latency for the higher priority interrupts. In case of RISCV I implemented my own priority levels different from the CLINT ones by simply disabling the the same and lower priority exceptions/interrupts (lower in my point of view) before enabling interrupts again. Of course, you should do all bookkeeping required to enable interrupts safely, so the higher priority interrupt can safely enter its handler. If an interrupt is the RISCV external interrupt I rely on the priorities configured by the user in the PLIC. User does it using the rtos.IRQ.Enable function. Generally the When it comes to testing, try find a way to rise a higher priority interrupt in the handler of the lower priority one and vice versa. In case of a typical MCU it's easy, because you can easily connect a GPIO pin to some interrupt input. Sometimes there may be a pure software way to do it. Here is how I did it for K210.
Yes, It is. User can use any Go in the interrupt handler, provided it doesn't allocate memory. Perhaps what confused you was the fact that the tasker itself must not use FPU. It's required to allow cheap context switching, e.g. you may check is it required to save/restore FPU context during switch.
Both noos/thumb and noos/riscv64 run user code in the user mode. It makes clear demarcation line between the user and the handler code. It's also required to use MPU (memory protection unit) in the ARMv7-M. But I can't give a strict justification for requiring this. You probably should implement cache maintenance operations if you use DMA. Without it you can't be sure that all data are in RAM before starting a DMA transaction or you read the content of cache instead of the RAM modified by DMA.
Ok. It's not required so much because of quite large RAM in N64. I wrote you some nonsense about using user/handler mode to protect the handler stack in the handler mode. Sometimes I write faster than I think. Forget about them. The TLB is probably the way to go.
Ok. Did you run ./all.bash on any MIPS64 linux system? |
In MIPS (on the VR4300) there are two software interrupts, which can be triggered by writing to a register. I use one of them to signal newwork(), and consider the other one unused and not exposed to the user. There is one timer interrupt, also unused as of now and considered not exposed to the user. Then there are the external interrupts, which I do consider exposed to the user. Only these support nesting. Currently I will only mask all pending interrupts, then reenable them. I didn't see rtos.IRQ.Enable. From what I read I need to implement sysirqctl for mips64 to support priorities correctly.
Then I should also add a call to savefprs in the externalInterruptHandler. It's already implemented, should be a quick fix.
I was very careful doing the caching correctly on the n64. But since I run in kernel mode all the time, I currently just have these functions which will call the cache ops directly. I should probably move this code to syscachemaint.
No 😏 Unfortunately I probably won't have the time to do so. |
For riscv64 I use qemu-system-riscv64 with small linux image. Now the same qemu-system-riscv64 is also used by noostest target. Try to install linux on the qemu-system-mips64 virtual machine. Below as a reference my script that starts my riscv64 one:
If started you can I think its crucial for your mips64 port to test it against the full set of standard tests (all.bash/run.bash) after any change to the mips code. You can try qemu-mips64 and run test on the linux/amd64 system provided the binfmt_misc is configured. It works for linux/thumb somehow and is more convenient for working on a single test. |
Ok, I will take a look at it. But there were good news in the meantime: I got feedback from one of the Go mips contributors. All of the revert commits can be removed. It was my fault by not initializing |
CLINT does almost the same for RISCV. Its timer is well designed and very useful.
Slow interrupts like some syscalls definitely should have the lowest priority and be preemptable. The tasker code should be considered very slow. Consider a lightpen/lightgun that uses IRQ to report seeing an electron beam. Its ISR should as fast as possible read the current vertical and horizontal position from DAC. The time for one pixel on the horizontal scan is 63.6μs / 320 = 0.2 μs or even less because apart from the visible horizontal pixels the line contains also sync and back/front porch. If this ISR sometime must wait for the context switch in the tasker or some slow system call the user experience will be bad. |
The terminology is a bit different in MIPS. Syscalls come as exceptions, not as interrupts and they aren't disabled at the beginning of the exceptionHandler. And my wording was bad: Nesting for fast syscalls is also impemented (not optional I think). Everything (external and software interrupts, exceptions) is preemptable as soon as possible. What was broken before was preemption done by the external interrupt and as a workaround I would have them always masked and only shortly enabled at safe points. This workaround is now gone and latencies should be as short as possible. To get a feeling of what's implemented, I recommend reading the comments in |
Yes. The terminology... RISCV has traps. Trap can be an exception or an interrupt. Exceptions are exceptional (syscall, illegal instruction, breakpoint, addres misalignment, etc.). There is no way to mask them. Interrupts can be internal or external. Every hart (core) has its mask register to mask any kind of interrupt. Internal interrupts may come from CLINT: software generated, timer generated. External interrupts come from PLIC. PLIC can handle (mask, prioritize) multiple exception sources and direct them to the one or more harts (cores). This CLINT+PLIC combo is only one of many possibilities. Cortex-M has only exceptions. Some exceptions are called interrupts. There is no clear distinction between internal and external interrupts. Maybe we need some unified terminology for this trap/exception/interrupt zoo.
I read it quickly and it refreshed my mind too. It seems to be based on the riscv64 code. The R26 and R27 are used for bootstrapping. It makes thing easier and faster than the RISCV mscratch register that allows you to free only one GPR for bootstrapping. What I understood is that now all three supported architectures (mips64, riscv64 and thumb) allow nesting interrupts and preempt exceptions like syscalls (fast and slow). One thing that should be done for mips64 is to implement priorities for external interrupts and allow user to configure them using the rtos package. |
Hi there, I started to play around a bit and am at a point where emgo generates an ELF file for MIPS. My ultimate goal would be running Go on a Nintendo64. Am I right with the assumption that I need to implement:
and I am basically ready to go? I only have basic understanding of writing assembly and having a hard time figuring out how to implement the tasker routines. Can you give me a starting point? Can I find MIPS assembly that does something similar somewhere?