a decently improved architecture
This commit is contained in:
parent
5c87063c48
commit
6d238b24c1
1115
Cargo.lock
generated
Normal file
1115
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[workspace]
|
||||||
|
resolver = "3"
|
||||||
|
members = ["rust"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
|
debug-assertions = true
|
142
build.gradle.kts
142
build.gradle.kts
|
@ -2,114 +2,126 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "2.1.10"
|
kotlin("jvm") version "2.1.10"
|
||||||
id("fabric-loom") version "1.9-SNAPSHOT"
|
id("fabric-loom") version "1.9-SNAPSHOT"
|
||||||
id("maven-publish")
|
id("maven-publish")
|
||||||
|
id("com.google.protobuf") version "0.9.4"
|
||||||
}
|
}
|
||||||
|
|
||||||
version = project.property("mod_version") as String
|
version = project.property("mod_version") as String
|
||||||
group = project.property("maven_group") as String
|
group = project.property("maven_group") as String
|
||||||
|
|
||||||
base {
|
base {
|
||||||
archivesName.set(project.property("archives_base_name") as String)
|
archivesName.set(project.property("archives_base_name") as String)
|
||||||
}
|
}
|
||||||
|
|
||||||
val targetJavaVersion = 21
|
val targetJavaVersion = 21
|
||||||
java {
|
java {
|
||||||
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
|
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
|
||||||
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
|
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
|
||||||
// if it is present.
|
// if it is present.
|
||||||
// If you remove this line, sources will not be generated.
|
// If you remove this line, sources will not be generated.
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
}
|
}
|
||||||
|
|
||||||
loom {
|
loom {
|
||||||
mods {
|
mods {
|
||||||
register("golemcomputers") {
|
register("golemcomputers") {
|
||||||
sourceSet("main")
|
sourceSet("main")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
// Add repositories to retrieve artifacts from in here.
|
// Add repositories to retrieve artifacts from in here.
|
||||||
// You should only use this when depending on other mods because
|
// You should only use this when depending on other mods because
|
||||||
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
|
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
|
||||||
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
|
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
|
||||||
// for more information about repositories.
|
// for more information about repositories.
|
||||||
|
}
|
||||||
|
|
||||||
|
protobuf {
|
||||||
|
protoc {
|
||||||
|
artifact = "com.google.protobuf:protoc:4.30.0"
|
||||||
|
}
|
||||||
|
generateProtoTasks {
|
||||||
|
file("./proto/all.proto")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
create("rustlib") {
|
create("rustlib") {
|
||||||
isCanBeConsumed = false
|
isCanBeConsumed = false
|
||||||
isCanBeResolved = true
|
isCanBeResolved = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// To change the versions see the gradle.properties file
|
// To change the versions see the gradle.properties file
|
||||||
minecraft("com.mojang:minecraft:${project.property("minecraft_version")}")
|
minecraft("com.mojang:minecraft:${project.property("minecraft_version")}")
|
||||||
mappings("net.fabricmc:yarn:${project.property("yarn_mappings")}:v2")
|
mappings("net.fabricmc:yarn:${project.property("yarn_mappings")}:v2")
|
||||||
modImplementation("net.fabricmc:fabric-loader:${project.property("loader_version")}")
|
modImplementation("net.fabricmc:fabric-loader:${project.property("loader_version")}")
|
||||||
modImplementation("net.fabricmc:fabric-language-kotlin:${project.property("kotlin_loader_version")}")
|
modImplementation("net.fabricmc:fabric-language-kotlin:${project.property("kotlin_loader_version")}")
|
||||||
|
|
||||||
modImplementation("net.fabricmc.fabric-api:fabric-api:${project.property("fabric_version")}")
|
modImplementation("net.fabricmc.fabric-api:fabric-api:${project.property("fabric_version")}")
|
||||||
add("rustlib", project(":rust"))
|
add("rustlib", project(":rust"))
|
||||||
implementation("com.github.jhg023:Pbbl:1.0.2")
|
implementation("com.github.jhg023:Pbbl:1.0.2")
|
||||||
|
implementation("com.google.protobuf:protobuf-java:4.30.0")
|
||||||
|
protobuf(files("proto/"))
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.processResources {
|
tasks.processResources {
|
||||||
inputs.property("version", project.version)
|
inputs.property("version", project.version)
|
||||||
inputs.property("minecraft_version", project.property("minecraft_version"))
|
inputs.property("minecraft_version", project.property("minecraft_version"))
|
||||||
inputs.property("loader_version", project.property("loader_version"))
|
inputs.property("loader_version", project.property("loader_version"))
|
||||||
filteringCharset = "UTF-8"
|
filteringCharset = "UTF-8"
|
||||||
|
|
||||||
from(configurations.getByName("rustlib"))
|
from(configurations.getByName("rustlib"))
|
||||||
|
|
||||||
filesMatching("fabric.mod.json") {
|
filesMatching("fabric.mod.json") {
|
||||||
expand(
|
expand(
|
||||||
"version" to project.version,
|
"version" to project.version,
|
||||||
"minecraft_version" to project.property("minecraft_version"),
|
"minecraft_version" to project.property("minecraft_version"),
|
||||||
"loader_version" to project.property("loader_version"),
|
"loader_version" to project.property("loader_version"),
|
||||||
"kotlin_loader_version" to project.property("kotlin_loader_version")
|
"kotlin_loader_version" to project.property("kotlin_loader_version")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<JavaCompile>().configureEach {
|
tasks.withType<JavaCompile>().configureEach {
|
||||||
// ensure that the encoding is set to UTF-8, no matter what the system default is
|
// ensure that the encoding is set to UTF-8, no matter what the system default is
|
||||||
// this fixes some edge cases with special characters not displaying correctly
|
// this fixes some edge cases with special characters not displaying correctly
|
||||||
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
|
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
|
||||||
// If Javadoc is generated, this must be specified in that task too.
|
// If Javadoc is generated, this must be specified in that task too.
|
||||||
options.encoding = "UTF-8"
|
options.encoding = "UTF-8"
|
||||||
options.release.set(targetJavaVersion)
|
options.release.set(targetJavaVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<KotlinCompile>().configureEach {
|
tasks.withType<KotlinCompile>().configureEach {
|
||||||
compilerOptions.jvmTarget.set(JvmTarget.fromTarget(targetJavaVersion.toString()))
|
compilerOptions.jvmTarget.set(JvmTarget.fromTarget(targetJavaVersion.toString()))
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.jar {
|
tasks.jar {
|
||||||
from("LICENSE") {
|
from("LICENSE") {
|
||||||
rename { "${it}_${project.base.archivesName}" }
|
rename { "${it}_${project.base.archivesName}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// configure the maven publication
|
// configure the maven publication
|
||||||
publishing {
|
publishing {
|
||||||
publications {
|
publications {
|
||||||
create<MavenPublication>("mavenJava") {
|
create<MavenPublication>("mavenJava") {
|
||||||
artifactId = project.property("archives_base_name") as String
|
artifactId = project.property("archives_base_name") as String
|
||||||
from(components["java"])
|
from(components["java"])
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
|
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
|
||||||
repositories {
|
repositories {
|
||||||
// Add repositories to publish to here.
|
// Add repositories to publish to here.
|
||||||
// Notice: This block does NOT have the same function as the block in the top level.
|
// Notice: This block does NOT have the same function as the block in the top level.
|
||||||
// The repositories here will be used for publishing your artifact, not for
|
// The repositories here will be used for publishing your artifact, not for
|
||||||
// retrieving dependencies.
|
// retrieving dependencies.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
cmake
|
cmake
|
||||||
ninja
|
ninja
|
||||||
pkg-config
|
pkg-config
|
||||||
|
protobuf
|
||||||
|
|
||||||
#needed for minecraft to function properly
|
#needed for minecraft to function properly
|
||||||
libGL
|
libGL
|
||||||
|
|
28
proto/all.proto
Normal file
28
proto/all.proto
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package computer;
|
||||||
|
|
||||||
|
option java_package = "ca.sanae.golemcomputers.computer";
|
||||||
|
option java_outer_classname = "NativeProtobuf";
|
||||||
|
|
||||||
|
message ComputerInit {
|
||||||
|
repeated ComponentInit components = 1;
|
||||||
|
|
||||||
|
uint32 primary_ram_pages = 2;
|
||||||
|
sint32 primary_rom_index = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ComponentInit {
|
||||||
|
oneof data {
|
||||||
|
CoreComponentInit core = 1;
|
||||||
|
ChatComponentInit chat = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message CoreComponentInit {
|
||||||
|
uint32 instructions_per_tick = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ChatComponentInit {
|
||||||
|
sint32 java_index = 1;
|
||||||
|
}
|
|
@ -10,15 +10,16 @@ crate-type = ["cdylib", "rlib"]
|
||||||
bitfield-struct = "0.10.1"
|
bitfield-struct = "0.10.1"
|
||||||
disarm64 = "0.1.24"
|
disarm64 = "0.1.24"
|
||||||
jni = "0.21.1"
|
jni = "0.21.1"
|
||||||
|
nodit = "0.9.2"
|
||||||
num-derive = "0.4.2"
|
num-derive = "0.4.2"
|
||||||
num-traits = "0.2.19"
|
num-traits = "0.2.19"
|
||||||
oneshot = "0.1.10"
|
oneshot = "0.1.10"
|
||||||
pollster = "0.4.0"
|
pollster = "0.4.0"
|
||||||
|
prost = "0.13.5"
|
||||||
smol = "2.0.2"
|
smol = "2.0.2"
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
unicorn-engine = { version = "2.1.1", default-features = false, features = ["arch_aarch64"], git = "https://github.com/Sanae6/unicorn/", branch = "cpreg" }
|
unicorn-engine = { version = "2.1.1", default-features = false, features = ["arch_aarch64"], git = "https://github.com/Sanae6/unicorn/", branch = "cpreg" }
|
||||||
zerocopy = { version = "0.8.20", features = ["derive"] }
|
zerocopy = { version = "0.8.20", features = ["derive"] }
|
||||||
|
|
||||||
[profile.release]
|
[build-dependencies]
|
||||||
panic = "abort"
|
prost-build = "0.13.5"
|
||||||
debug-assertions = true
|
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
val task = tasks.register<Exec>("default") {
|
val task = tasks.register<Exec>("default") {
|
||||||
commandLine("cargo", "build", "--release")
|
commandLine("cargo", "build", "--release")
|
||||||
val library = System.mapLibraryName("golem_computers")
|
val library = System.mapLibraryName("golem_computers")
|
||||||
val libraryPath = "$workingDir/target/release/$library"
|
val libraryPath = "$workingDir/../target/release/$library"
|
||||||
outputs.file(libraryPath)
|
outputs.file(libraryPath)
|
||||||
inputs.dir("$workingDir/src")
|
inputs.dir("$workingDir/src")
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
if (!file(libraryPath).exists()) {
|
if (!file(libraryPath).exists()) {
|
||||||
throw GradleException("the library wasn't properly build! expected $libraryPath")
|
throw GradleException("the library wasn't properly built! expected $libraryPath")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
create("default") {
|
create("default")
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
artifacts.add("default", task)
|
artifacts.add("default", task)
|
6
rust/build.rs
Normal file
6
rust/build.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
use std::io::Result;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
prost_build::compile_protos(&["../proto/all.proto"], &["../proto"])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
37
rust/examples/condvar.rs
Normal file
37
rust/examples/condvar.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use std::{
|
||||||
|
sync::{Arc, Condvar, Mutex},
|
||||||
|
thread,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let pair = Arc::new((Mutex::new(false), Condvar::new()));
|
||||||
|
let pair_clone1 = Arc::clone(&pair);
|
||||||
|
let pair_clone2 = Arc::clone(&pair);
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let (lock, cvar) = &*pair_clone1;
|
||||||
|
let mut started = lock.lock().unwrap();
|
||||||
|
*started = true;
|
||||||
|
std::thread::sleep(Duration::from_secs(3));
|
||||||
|
cvar.notify_all();
|
||||||
|
});
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let (lock, cvar) = &*pair_clone2;
|
||||||
|
let mut started = lock.lock().unwrap();
|
||||||
|
while !*started {
|
||||||
|
println!("waiting 2");
|
||||||
|
started = cvar.wait(started).unwrap();
|
||||||
|
}
|
||||||
|
println!("Thread 2 proceeding...");
|
||||||
|
});
|
||||||
|
|
||||||
|
let (lock, cvar) = &*pair;
|
||||||
|
let mut started = lock.lock().unwrap();
|
||||||
|
while !*started {
|
||||||
|
println!("waiting 1");
|
||||||
|
started = cvar.wait(started).unwrap();
|
||||||
|
}
|
||||||
|
println!("Thread 1 proceeding...");
|
||||||
|
}
|
|
@ -1,251 +0,0 @@
|
||||||
use std::{
|
|
||||||
mem::offset_of,
|
|
||||||
ops::{Deref, DerefMut, Range},
|
|
||||||
pin::Pin,
|
|
||||||
sync::{atomic::Ordering, Arc},
|
|
||||||
};
|
|
||||||
|
|
||||||
use bitfield_struct::bitfield;
|
|
||||||
use jni::JavaVM;
|
|
||||||
use unicorn_engine::Unicorn;
|
|
||||||
use zerocopy::{
|
|
||||||
big_endian::U16 as U16BE,
|
|
||||||
little_endian::{U16, U32, U64},
|
|
||||||
FromBytes, Immutable, IntoBytes, KnownLayout,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
align_up, component::{Component, ComponentInterface}, core::CpuContext, memory_map::RAM_START, overlapping, range_of_field, unsync_cell::UnsyncCell, unsync_read
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::ComponentDma;
|
|
||||||
|
|
||||||
pub struct ComponentIoResult {
|
|
||||||
component_index: usize,
|
|
||||||
direction: ComponentIoDirection,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ComponentIoDirection {
|
|
||||||
ToComponent,
|
|
||||||
ToGuest,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component {
|
|
||||||
pub fn perform_write<D>(
|
|
||||||
&self,
|
|
||||||
range: Range<usize>,
|
|
||||||
writer: impl FnOnce(&UnsyncCell<ComponentInterface>),
|
|
||||||
ram: Arc<UnsyncCell<[u8]>>,
|
|
||||||
java_vm: JavaVM,
|
|
||||||
) -> Option<Pin<Box<dyn Future<Output = ComponentIoResult>>>> {
|
|
||||||
let Self::Populated {
|
|
||||||
read_transfer_active,
|
|
||||||
write_transfer_active,
|
|
||||||
info,
|
|
||||||
interface,
|
|
||||||
own_index,
|
|
||||||
java_component,
|
|
||||||
..
|
|
||||||
} = self
|
|
||||||
else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
if overlapping(&range, &range_of_field!(ComponentInterface, info)) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let read_range = range_of_field!(ComponentInterface, read_dma);
|
|
||||||
if overlapping(&range, &read_range) {
|
|
||||||
if read_transfer_active.load(Ordering::Acquire) || read_range != range {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
writer(interface);
|
|
||||||
|
|
||||||
let dma: ComponentDma = unsync_read!(interface, ComponentInterface, read_dma);
|
|
||||||
if dma.dma_enabled()
|
|
||||||
&& read_transfer_active.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire).is_ok()
|
|
||||||
{
|
|
||||||
if !overlapping(
|
|
||||||
&(dma.address..dma.address + dma.size()),
|
|
||||||
&(RAM_START..RAM_START + ram.len() as u64),
|
|
||||||
) {
|
|
||||||
read_transfer_active.store(false, Ordering::Release);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let bytes_per_chunk: u32 = info.bytes_per_tick / 5;
|
|
||||||
let java_component =
|
|
||||||
let index = return Some(Box::pin(async move {
|
|
||||||
let java_component = java_component;
|
|
||||||
ComponentIoResult { component_index: 0, direction: todo!() }
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// let write_range = range_of_field!(ComponentInterface, write_dma);
|
|
||||||
// if write_transfer_active && overlapping(range.clone(), write_range) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
|
|
||||||
#[repr(C)]
|
|
||||||
struct ComponentMmioHeader {
|
|
||||||
component_count: U32,
|
|
||||||
/// version bump when component access method (offset table) changes or [ComponentInfo] is non-additively changed
|
|
||||||
version: U16,
|
|
||||||
control: U16BE,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitfield(u16)]
|
|
||||||
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
|
|
||||||
pub struct ComponentMmioControl {
|
|
||||||
interrupt_added: bool,
|
|
||||||
interrupt_removed: bool,
|
|
||||||
#[bits(2)]
|
|
||||||
_reserved: U16,
|
|
||||||
/// read-write, indicates the core to fire interrupts on. indicating an invalid core means no core will receive the interrupt
|
|
||||||
#[bits(4)]
|
|
||||||
interrupt_firing_core: u8,
|
|
||||||
#[bits(8)]
|
|
||||||
_reserved: U16,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn calculate_components_start(component_count: usize) -> usize {
|
|
||||||
OFFSETS_START + align_up(component_count as u64 * 4 + 16, 0x1000) as usize
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_bytes_to_u64(bytes: &[u8]) -> u64 {
|
|
||||||
match bytes.len() {
|
|
||||||
1 => bytes[0] as u64,
|
|
||||||
2 => U16::read_from_bytes(bytes).unwrap().get() as u64,
|
|
||||||
4 => U32::read_from_bytes(bytes).unwrap().get() as u64,
|
|
||||||
8 => U64::read_from_bytes(bytes).unwrap().get() as u64,
|
|
||||||
16 => todo!("128 bit reads are currently neither implemented here nor supported by unicorn hooks"),
|
|
||||||
_ => panic!("expected byte slice of primitive size"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_bytes_from_u64(bytes: &mut [u8], value: u64) {
|
|
||||||
match bytes.len() {
|
|
||||||
1 => bytes[0] = value.try_into().unwrap(),
|
|
||||||
2 => U16::new(value.try_into().unwrap()).write_to(bytes).unwrap(),
|
|
||||||
4 => U32::new(value.try_into().unwrap()).write_to(bytes).unwrap(),
|
|
||||||
8 => U64::new(value).write_to(bytes).unwrap(),
|
|
||||||
16 => todo!("128 bit writes are currently neither implemented here nor supported by unicorn hooks"),
|
|
||||||
_ => panic!("expected byte slice of primitive size"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const OFFSETS_START: usize = 0x200;
|
|
||||||
const INTERFACE_INTERVAL: usize = 0x800;
|
|
||||||
|
|
||||||
pub fn component_mmio_read(vcpu: &mut Unicorn<CpuContext>, offset: u64, size: usize) -> u64 {
|
|
||||||
let offset = offset as usize;
|
|
||||||
let component_count = vcpu.get_data().components.len();
|
|
||||||
if offset < OFFSETS_START {
|
|
||||||
// mmio header
|
|
||||||
println!("reading from mmio start!");
|
|
||||||
let header = ComponentMmioHeader {
|
|
||||||
component_count: U32::new(component_count as u32),
|
|
||||||
version: U16::new(0),
|
|
||||||
control: U16BE::new(vcpu.get_data().component_control.read().into_bits()),
|
|
||||||
};
|
|
||||||
if offset > size_of::<ComponentMmioHeader>() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
let mut region = [0; size_of::<ComponentMmioHeader>() + size_of::<u64>()];
|
|
||||||
header.write_to_prefix(&mut region).unwrap();
|
|
||||||
let range = offset as usize..(offset + size) as usize;
|
|
||||||
return read_bytes_to_u64(®ion[range]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if offset < vcpu.get_data().component_data_start {
|
|
||||||
// offsets
|
|
||||||
let range = offset as usize..(offset + size) as usize;
|
|
||||||
let components_range = OFFSETS_START..(size_of::<u32>() * component_count);
|
|
||||||
|
|
||||||
let range = range.start - components_range.start as usize..range.end - components_range.start as usize;
|
|
||||||
let first_offset_index = range.start >> 2;
|
|
||||||
let offset_buffer: [U32; 3] = std::array::from_fn(|index| {
|
|
||||||
(first_offset_index + index < component_count as usize)
|
|
||||||
.then_some(U32::new(
|
|
||||||
((first_offset_index + index) * INTERFACE_INTERVAL as usize) as u32,
|
|
||||||
))
|
|
||||||
.unwrap_or_default()
|
|
||||||
});
|
|
||||||
|
|
||||||
return read_bytes_to_u64(&offset_buffer.as_bytes()[range]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// info region
|
|
||||||
|
|
||||||
let offset = offset - vcpu.get_data().component_data_start;
|
|
||||||
let index = offset / INTERFACE_INTERVAL as usize;
|
|
||||||
let offset = offset - (index * INTERFACE_INTERVAL as usize);
|
|
||||||
|
|
||||||
let Some(Component::Populated { interface, .. }) = vcpu.get_data().components.get(index) else {
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
match size {
|
|
||||||
1 => interface.read_into::<u8>(offset).into(),
|
|
||||||
2 => interface.read_into::<U16>(offset).into(),
|
|
||||||
4 => interface.read_into::<U32>(offset).into(),
|
|
||||||
8 => interface.read_into::<U64>(offset).into(),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn component_mmio_write(vcpu: &mut Unicorn<CpuContext>, offset: u64, size: usize, value: u64) {
|
|
||||||
let offset = offset as usize;
|
|
||||||
let range = offset..offset + size;
|
|
||||||
|
|
||||||
if offset < OFFSETS_START {
|
|
||||||
if overlapping(
|
|
||||||
&range,
|
|
||||||
&(offset_of!(ComponentMmioHeader, control)..size_of::<ComponentMmioHeader>()),
|
|
||||||
) {
|
|
||||||
// control included
|
|
||||||
let mut buffer = [0; size_of::<ComponentMmioHeader>() + size_of::<u64>()];
|
|
||||||
write_bytes_from_u64(&mut buffer[range], value);
|
|
||||||
let header = ComponentMmioHeader::ref_from_prefix(&buffer).unwrap().0;
|
|
||||||
|
|
||||||
vcpu.get_data().component_control.write(ComponentMmioControl::from_bits(header.control.get()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if offset < vcpu.get_data().component_data_start {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let offset = offset - vcpu.get_data().component_data_start;
|
|
||||||
let index = offset / INTERFACE_INTERVAL as usize;
|
|
||||||
let offset = offset - (index * INTERFACE_INTERVAL as usize);
|
|
||||||
|
|
||||||
let Some(component) = vcpu.get_data().components.get(index) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if offset > size_of::<ComponentInterface>() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
component.perform_write(
|
|
||||||
range,
|
|
||||||
|interface| match size {
|
|
||||||
1 => interface.write_from(offset, &(value as u8)),
|
|
||||||
2 => interface.write_from(offset, &U16::new(value as u16)),
|
|
||||||
4 => interface.write_from(offset, &U32::new(value as u32)),
|
|
||||||
8 => interface.write_from(offset, &U64::new(value as u64)),
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
vcpu.get_data().ram,
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
pub mod mmio;
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
cell::RefCell,
|
|
||||||
mem::offset_of,
|
|
||||||
ops::{Deref, DerefMut, Range, RangeBounds},
|
|
||||||
pin::Pin,
|
|
||||||
rc::Rc,
|
|
||||||
sync::{
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
mpsc, Arc, LazyLock, Mutex,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use bitfield_struct::bitfield;
|
|
||||||
use jni::objects::GlobalRef;
|
|
||||||
use unicorn_engine::Unicorn;
|
|
||||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
core::CpuContext, memory_map::RAM_START, overlapping, range_of_field, subtract_range, unsync_cell::UnsyncCell,
|
|
||||||
unsync_read,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub enum Component {
|
|
||||||
#[default]
|
|
||||||
Empty,
|
|
||||||
Populated {
|
|
||||||
own_index: usize,
|
|
||||||
java_component: GlobalRef,
|
|
||||||
read_transfer_active: AtomicBool,
|
|
||||||
write_transfer_active: AtomicBool,
|
|
||||||
info: ComponentInfo,
|
|
||||||
interface: UnsyncCell<ComponentInterface>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct ComponentInterface {
|
|
||||||
// read only
|
|
||||||
pub info: ComponentInfo,
|
|
||||||
|
|
||||||
// read-write
|
|
||||||
control: ComponentControl,
|
|
||||||
_restart_device: u8,
|
|
||||||
_reserved: [u8; 6],
|
|
||||||
read_dma: ComponentDma,
|
|
||||||
write_dma: ComponentDma,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitfield(u8)]
|
|
||||||
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
|
|
||||||
pub struct ComponentControl {
|
|
||||||
interrupt_when_added: bool,
|
|
||||||
interrupt_when_removed: bool,
|
|
||||||
#[bits(2)]
|
|
||||||
_reserved: u8,
|
|
||||||
|
|
||||||
#[bits(4)]
|
|
||||||
interrupt_firing_core: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct ComponentDma {
|
|
||||||
pub upper_word: ComponentDmaUpper,
|
|
||||||
pub address: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for ComponentDma {
|
|
||||||
type Target = ComponentDmaUpper;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.upper_word
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for ComponentDma {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.upper_word
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bitfield(u64)]
|
|
||||||
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
|
|
||||||
pub struct ComponentDmaUpper {
|
|
||||||
dma_enabled: bool,
|
|
||||||
#[bits(3)]
|
|
||||||
_reserved: u8,
|
|
||||||
/// read-write, indicates the core to fire interrupts on. indicating an invalid core means no core will receive the interrupt
|
|
||||||
#[bits(4)]
|
|
||||||
interrupt_firing_core: u8,
|
|
||||||
|
|
||||||
#[bits(56)]
|
|
||||||
size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct ComponentInfo {
|
|
||||||
/// a null terminated string, the type name of the component
|
|
||||||
pub type_name: [u8; 256],
|
|
||||||
/// a null terminated string, the protocol identifier for how to talk to the component
|
|
||||||
pub protocol: [u8; 256],
|
|
||||||
pub bytes_per_tick: u32,
|
|
||||||
pub version: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ComponentInfo {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
type_name: [0; 256],
|
|
||||||
protocol: [0; 256],
|
|
||||||
bytes_per_tick: 0,
|
|
||||||
version: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
72
rust/src/components/core.rs
Normal file
72
rust/src/components/core.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use std::{mem::offset_of, sync::{mpsc, Arc}};
|
||||||
|
|
||||||
|
use bitfield_struct::bitfield;
|
||||||
|
use zerocopy::{
|
||||||
|
little_endian::{U32, U64},
|
||||||
|
FromBytes, IntoBytes, KnownLayout,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{component_name, core::CoreMessage, overlapping, unsync_cell::UnsyncCell};
|
||||||
|
|
||||||
|
use super::Component;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, FromBytes, IntoBytes, KnownLayout)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Registers {
|
||||||
|
pub reset_address: U64,
|
||||||
|
pub flags: U32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct Flags {
|
||||||
|
should_reset: bool,
|
||||||
|
#[bits(31)]
|
||||||
|
_reserved: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CoreComponent {
|
||||||
|
registers: Arc<UnsyncCell<Registers>>,
|
||||||
|
message_sender: mpsc::Sender<CoreMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CoreComponent {
|
||||||
|
pub fn new(registers: Arc<UnsyncCell<Registers>>, message_sender: mpsc::Sender<CoreMessage>) -> Self {
|
||||||
|
Self {
|
||||||
|
registers,
|
||||||
|
message_sender,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for CoreComponent {
|
||||||
|
fn name() -> [u8; 0x30] {
|
||||||
|
component_name!(b"core")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn memory_map_size(&self) -> u64 {
|
||||||
|
size_of::<Registers>() as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&self, _: &mut jni::JNIEnv, offset: u64, data: &mut [u8]) {
|
||||||
|
assert!((offset + data.len() as u64) < self.memory_map_size() as u64);
|
||||||
|
self.registers.read_into_byte_slice(offset as usize, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&self, _: &mut jni::JNIEnv, offset: u64, data: &[u8]) {
|
||||||
|
assert!((offset + data.len() as u64) < self.memory_map_size() as u64);
|
||||||
|
self.registers.write_from_byte_slice(offset as usize, data);
|
||||||
|
|
||||||
|
if overlapping(
|
||||||
|
&(offset..offset + data.len() as u64),
|
||||||
|
&(offset_of!(Registers, flags) as u64..size_of::<Registers>() as u64),
|
||||||
|
) {
|
||||||
|
let mut registers = self.registers.read();
|
||||||
|
let flags = Flags::from_bits(registers.flags.get());
|
||||||
|
if flags.should_reset() {
|
||||||
|
let _ = self.message_sender.send(CoreMessage::Reset);
|
||||||
|
registers.flags.set(flags.with_should_reset(false).into_bits());
|
||||||
|
self.registers.write(registers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
rust/src/components/debug_chat.rs
Normal file
50
rust/src/components/debug_chat.rs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use jni::{
|
||||||
|
objects::{GlobalRef, JValueGen},
|
||||||
|
JNIEnv,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::component_name;
|
||||||
|
|
||||||
|
use super::Component;
|
||||||
|
|
||||||
|
pub struct DebugChat {
|
||||||
|
buffer: Mutex<String>,
|
||||||
|
object: GlobalRef,
|
||||||
|
}
|
||||||
|
impl DebugChat {
|
||||||
|
pub(crate) fn new(component: GlobalRef) -> Self {
|
||||||
|
Self {
|
||||||
|
buffer: Default::default(),
|
||||||
|
object: component,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for DebugChat {
|
||||||
|
fn name() -> [u8; 0x30] {
|
||||||
|
component_name!(b"chat")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn memory_map_size(&self) -> u64 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&self, env: &mut JNIEnv, _: u64, data: &[u8]) {
|
||||||
|
let mut string = self.buffer.lock().unwrap();
|
||||||
|
if data[0] == 0 {
|
||||||
|
let jni_string = env.new_string(string.drain(..).collect::<String>()).expect("failed to create string");
|
||||||
|
env
|
||||||
|
.call_method(
|
||||||
|
self.object.clone(),
|
||||||
|
"sendChatMessage",
|
||||||
|
"(Ljava_lang_String;)V",
|
||||||
|
&[JValueGen::Object(&jni_string)],
|
||||||
|
)
|
||||||
|
.expect("failed to call...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
string.push(data[0].into());
|
||||||
|
}
|
||||||
|
}
|
29
rust/src/components/interrupt_controller.rs
Normal file
29
rust/src/components/interrupt_controller.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use bitfield_struct::bitfield;
|
||||||
|
|
||||||
|
use crate::component_name;
|
||||||
|
|
||||||
|
use super::Component;
|
||||||
|
|
||||||
|
#[bitfield(u8)]
|
||||||
|
struct InterruptEntry {
|
||||||
|
#[bits(4)]
|
||||||
|
affinity: u8,
|
||||||
|
masked: bool,
|
||||||
|
fired: bool,
|
||||||
|
#[bits(2)]
|
||||||
|
_unused: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InterruptController {
|
||||||
|
_interrupt_entries: [InterruptEntry; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for InterruptController {
|
||||||
|
fn name() -> [u8; 0x30] {
|
||||||
|
component_name!(b"interrupt_controller")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn memory_map_size(&self) -> u64 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
66
rust/src/components/mod.rs
Normal file
66
rust/src/components/mod.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use core::CoreComponent;
|
||||||
|
|
||||||
|
use debug_chat::DebugChat;
|
||||||
|
use jni::JNIEnv;
|
||||||
|
|
||||||
|
pub mod core;
|
||||||
|
pub mod debug_chat;
|
||||||
|
pub mod interrupt_controller;
|
||||||
|
pub mod storage;
|
||||||
|
|
||||||
|
pub trait Component: Sized {
|
||||||
|
fn name() -> [u8; 0x30];
|
||||||
|
fn memory_map_size(&self) -> u64;
|
||||||
|
|
||||||
|
fn write(&self, env: &mut JNIEnv, offset: u64, data: &[u8]) {
|
||||||
|
let _ = (env, offset, data);
|
||||||
|
}
|
||||||
|
fn read(&self, env: &mut JNIEnv, offset: u64, data: &mut [u8]) {
|
||||||
|
let _ = (env, offset, data);
|
||||||
|
}
|
||||||
|
fn has_interrupt(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum AllComponent {
|
||||||
|
Core(CoreComponent),
|
||||||
|
DebugChat(DebugChat),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AllComponent {
|
||||||
|
pub fn name(&self) -> [u8; 0x30] {
|
||||||
|
match self {
|
||||||
|
AllComponent::Core(_) => CoreComponent::name(),
|
||||||
|
AllComponent::DebugChat(_) => DebugChat::name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn memory_map_size(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
AllComponent::Core(core_component) => core_component.memory_map_size(),
|
||||||
|
AllComponent::DebugChat(debug_chat) => debug_chat.memory_map_size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&self, env: &mut JNIEnv, offset: u64, data: &[u8]) {
|
||||||
|
match self {
|
||||||
|
AllComponent::Core(core_component) => core_component.write(env, offset, data),
|
||||||
|
AllComponent::DebugChat(debug_chat) => debug_chat.write(env, offset, data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&self, env: &mut JNIEnv, offset: u64, data: &mut [u8]) {
|
||||||
|
match self {
|
||||||
|
AllComponent::Core(core_component) => core_component.read(env, offset, data),
|
||||||
|
AllComponent::DebugChat(debug_chat) => debug_chat.read(env, offset, data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_interrupt(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
AllComponent::Core(core_component) => core_component.has_interrupt(),
|
||||||
|
AllComponent::DebugChat(debug_chat) => debug_chat.has_interrupt(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
rust/src/components/storage.rs
Normal file
2
rust/src/components/storage.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub struct Storage {
|
||||||
|
}
|
|
@ -1,86 +1,114 @@
|
||||||
use std::sync::{Arc, OnceLock, RwLock};
|
pub(crate) mod proto {
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/computer.rs"));
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::sync::{mpsc, Arc};
|
||||||
|
|
||||||
use jni::{
|
use jni::{
|
||||||
objects::{JByteBuffer, JIntArray, JObject, JString, JValue},
|
objects::{JByteArray, JByteBuffer, JObject, JObjectArray, JValue},
|
||||||
signature::{Primitive, ReturnType},
|
signature::{Primitive, ReturnType},
|
||||||
strings::JNIString,
|
sys::jlong,
|
||||||
sys::{jint, jlong},
|
|
||||||
JNIEnv,
|
JNIEnv,
|
||||||
};
|
};
|
||||||
|
use prost::Message;
|
||||||
|
use proto::component_init::Data;
|
||||||
|
use zerocopy::{little_endian::U64, FromZeros};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
assert_positive,
|
align_up_to_page,
|
||||||
component::{mmio::ComponentMmioControl, Component, ComponentInfo, ComponentInterface},
|
components::{
|
||||||
core::CoreHandle,
|
core::{CoreComponent, Registers},
|
||||||
|
debug_chat::DebugChat,
|
||||||
|
AllComponent,
|
||||||
|
},
|
||||||
|
core::{spawn_core_thread, Core, CoreMessage},
|
||||||
|
device_bus::{DeviceBus, ROM_START},
|
||||||
unsync_cell::UnsyncCell,
|
unsync_cell::UnsyncCell,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Computer {
|
pub struct Computer {
|
||||||
pub components: Arc<[RwLock<Component>]>,
|
cores: Vec<mpsc::Sender<CoreMessage>>,
|
||||||
pub component_control: Arc<UnsyncCell<ComponentMmioControl>>,
|
}
|
||||||
pub shared_ram: Arc<UnsyncCell<[u8]>>,
|
|
||||||
pub shared_rom: Arc<UnsyncCell<[u8]>>,
|
|
||||||
|
|
||||||
pub cores: OnceLock<Box<[CoreHandle]>>,
|
fn try_get_object<'local>(
|
||||||
|
env: &mut JNIEnv<'local>,
|
||||||
|
init_objects: &JObjectArray<'local>,
|
||||||
|
index: i32,
|
||||||
|
) -> jni::errors::Result<Option<JObject<'local>>> {
|
||||||
|
if index < 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
env.get_object_array_element(&init_objects, index).map(Some)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Computer {
|
impl Computer {
|
||||||
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_new")]
|
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_new")]
|
||||||
extern "C" fn new<'local>(
|
unsafe extern "C" fn new<'local>(
|
||||||
mut env: JNIEnv<'local>,
|
mut env: JNIEnv<'local>,
|
||||||
java_computer: JObject<'local>,
|
java_computer: JObject<'local>,
|
||||||
component_capacity: i32,
|
init_data: JByteArray<'local>,
|
||||||
memory_size: i32,
|
init_objects: JObjectArray<'local>,
|
||||||
core_speeds: JIntArray<'local>,
|
|
||||||
) {
|
) {
|
||||||
let (component_capacity, memory_size): (usize, usize) = (
|
let init_data = env.convert_byte_array(&init_data).expect("unable to convert byte array to vec");
|
||||||
assert_positive!(env, component_capacity),
|
let init = proto::ComputerInit::decode(init_data.as_slice()).expect("failed to decode init data from java!");
|
||||||
assert_positive!(env, memory_size),
|
let ram = UnsyncCell::new_zeroed_box(init.primary_ram_pages as usize * 0x1000);
|
||||||
);
|
|
||||||
|
|
||||||
let core_count = env.get_array_length(&core_speeds).unwrap() as usize;
|
let rom = try_get_object(&mut env, &init_objects, init.primary_rom_index)
|
||||||
if core_count > 16 {
|
.expect("failed to get rom from java")
|
||||||
env.throw("no more than 16 cores are allowed").unwrap();
|
.map(|rom| {
|
||||||
return;
|
if !env.is_instance_of(&rom, "java/nio/ByteBuffer").expect("failed to perform instanceOf") {
|
||||||
}
|
panic!("rom was not a byte buffer")
|
||||||
println!("got to computer construction!");
|
}
|
||||||
let computer = Box::new(Self {
|
let rom_byte_buf = JByteBuffer::from(rom);
|
||||||
components: {
|
let capacity = env.get_direct_buffer_capacity(&rom_byte_buf).expect("unable to extract capacity from rom");
|
||||||
let mut components = Vec::with_capacity(component_capacity);
|
let rom = UnsyncCell::new_zeroed_box(align_up_to_page(capacity));
|
||||||
components.extend(std::iter::from_fn(|| Default::default()).take(component_capacity));
|
rom.update_from_jni(&mut env, &rom_byte_buf).unwrap();
|
||||||
Arc::from(components.into_boxed_slice())
|
rom
|
||||||
},
|
|
||||||
component_control: Arc::new(UnsyncCell::new(ComponentMmioControl::new())),
|
|
||||||
shared_ram: Arc::from(UnsyncCell::new_zeroed_box(memory_size as usize)),
|
|
||||||
shared_rom: Arc::from(UnsyncCell::new_zeroed_box(0x1000000 as usize)),
|
|
||||||
// late initalized, see below
|
|
||||||
cores: OnceLock::new(),
|
|
||||||
});
|
|
||||||
|
|
||||||
println!("core construction");
|
|
||||||
let mut speeds = [0; 16];
|
|
||||||
env.get_int_array_region(core_speeds, 0, &mut speeds[..core_count]).unwrap();
|
|
||||||
println!("core speeds acquired {:?}", &speeds[..core_count]);
|
|
||||||
let Ok(cores) = speeds
|
|
||||||
.into_iter()
|
|
||||||
.take(core_count)
|
|
||||||
.enumerate()
|
|
||||||
.map(|(core_index, speed)| {
|
|
||||||
Ok(CoreHandle::new(
|
|
||||||
&computer,
|
|
||||||
core_index,
|
|
||||||
assert_positive!(env, speed, Err(())),
|
|
||||||
env.get_java_vm().unwrap(),
|
|
||||||
))
|
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.expect("no rom was provided");
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = computer.cores.set(cores.into_boxed_slice());
|
let mut core_init_datas = Vec::new();
|
||||||
|
let components = init
|
||||||
|
.components
|
||||||
|
.into_iter()
|
||||||
|
.map(|component| match component.data.unwrap() {
|
||||||
|
Data::Core(init) => {
|
||||||
|
let registers = Arc::new(UnsyncCell::new(Registers {
|
||||||
|
reset_address: U64::new(ROM_START),
|
||||||
|
..Registers::new_zeroed()
|
||||||
|
}));
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
core_init_datas.push((init, registers.clone(), sender.clone(), receiver));
|
||||||
|
AllComponent::Core(CoreComponent::new(registers, sender))
|
||||||
|
}
|
||||||
|
Data::Chat(init) => {
|
||||||
|
let component = try_get_object(&mut env, &init_objects, init.java_index)
|
||||||
|
.expect("failed to get chat component from java")
|
||||||
|
.expect("chat component object was not provided");
|
||||||
|
|
||||||
println!("storing computer info in field!");
|
let component = env.new_global_ref(component).unwrap();
|
||||||
|
|
||||||
|
AllComponent::DebugChat(DebugChat::new(component))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let device_bus = Arc::new(DeviceBus::new(ram, rom, components));
|
||||||
|
|
||||||
|
let cores = core_init_datas
|
||||||
|
.into_iter()
|
||||||
|
.map(|(init, registers, sender, receiver)| {
|
||||||
|
spawn_core_thread(
|
||||||
|
Core::new(registers, device_bus.clone(), init.instructions_per_tick),
|
||||||
|
env.get_java_vm().unwrap(),
|
||||||
|
receiver,
|
||||||
|
);
|
||||||
|
sender
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let computer = Box::new(Self { cores });
|
||||||
|
|
||||||
env
|
env
|
||||||
.set_field(
|
.set_field(
|
||||||
|
@ -104,67 +132,6 @@ impl Computer {
|
||||||
.unwrap() as *mut _
|
.unwrap() as *mut _
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_insertComponentNative")]
|
|
||||||
unsafe extern "C" fn insert_component<'local>(
|
|
||||||
mut env: JNIEnv<'local>,
|
|
||||||
computer: JObject<'local>,
|
|
||||||
component_index: jint,
|
|
||||||
component_object: JObject<'local>,
|
|
||||||
type_name: JString<'local>,
|
|
||||||
protocol: JString<'local>,
|
|
||||||
bytes_per_tick: u32,
|
|
||||||
version: u32,
|
|
||||||
) {
|
|
||||||
// Safety: the responsibility of asserting the pointer is valid is on the java caller
|
|
||||||
let computer = unsafe { &*Self::from_jni(&mut env, computer) };
|
|
||||||
|
|
||||||
let component_index: usize = assert_positive!(env, component_index);
|
|
||||||
let mut make_info_string = |string: JString<'local>| {
|
|
||||||
let mut bytes = [0u8; 256];
|
|
||||||
let jstring = env.get_string(&string).unwrap();
|
|
||||||
|
|
||||||
let length = bytes.len().min(jstring.to_bytes().len());
|
|
||||||
bytes[..length].copy_from_slice(&jstring.to_bytes()[..length]);
|
|
||||||
|
|
||||||
bytes
|
|
||||||
};
|
|
||||||
let type_name = make_info_string(type_name);
|
|
||||||
let protocol = make_info_string(protocol);
|
|
||||||
let info = ComponentInfo {
|
|
||||||
type_name,
|
|
||||||
protocol,
|
|
||||||
bytes_per_tick,
|
|
||||||
version,
|
|
||||||
};
|
|
||||||
let java_component = env.new_global_ref(component_object).unwrap();
|
|
||||||
*computer.components[component_index].write().unwrap() = Component::Populated {
|
|
||||||
own_index: component_index,
|
|
||||||
java_component,
|
|
||||||
read_transfer_active: false.into(),
|
|
||||||
write_transfer_active: false.into(),
|
|
||||||
info,
|
|
||||||
interface: ComponentInterface::default().into(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_updateRomTest")]
|
|
||||||
unsafe extern "C" fn update_rom_test<'local>(
|
|
||||||
mut env: JNIEnv<'local>,
|
|
||||||
computer: JObject<'local>,
|
|
||||||
bytes: JByteBuffer<'local>,
|
|
||||||
) {
|
|
||||||
// Safety: the responsibility of asserting the pointer is valid is on the java caller
|
|
||||||
let computer = unsafe { &*Self::from_jni(&mut env, computer) };
|
|
||||||
|
|
||||||
computer.shared_rom.update_from_jni(&mut env, &bytes).expect("failed to update rom");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_updateRomFile")]
|
|
||||||
unsafe extern "C" fn update_rom_file<'local>(env: JNIEnv<'local>, computer: JObject<'local>) {
|
|
||||||
drop((env, computer));
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_free")]
|
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_free")]
|
||||||
unsafe extern "C" fn free<'local>(mut env: JNIEnv<'local>, computer: JObject<'local>) {
|
unsafe extern "C" fn free<'local>(mut env: JNIEnv<'local>, computer: JObject<'local>) {
|
||||||
let computer = Self::from_jni(&mut env, computer);
|
let computer = Self::from_jni(&mut env, computer);
|
||||||
|
@ -172,31 +139,23 @@ impl Computer {
|
||||||
drop(unsafe { Box::from_raw(computer) })
|
drop(unsafe { Box::from_raw(computer) })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_wakeCoreAt")]
|
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_resetCore")]
|
||||||
unsafe extern "C" fn wake_core_at<'local>(
|
unsafe extern "C" fn reset_core<'local>(mut env: JNIEnv<'local>, computer: JObject<'local>, core: u32, wake: bool) {
|
||||||
mut env: JNIEnv<'local>,
|
|
||||||
computer: JObject<'local>,
|
|
||||||
core: jint,
|
|
||||||
address: jlong,
|
|
||||||
) {
|
|
||||||
let (core, address): (usize, u64) = (assert_positive!(env, core), assert_positive!(env, address));
|
|
||||||
// Safety: the responsibility of asserting the pointer is valid is on the java caller
|
// Safety: the responsibility of asserting the pointer is valid is on the java caller
|
||||||
let computer = unsafe { &*Self::from_jni(&mut env, computer) };
|
let computer = unsafe { &*Self::from_jni(&mut env, computer) };
|
||||||
|
let core = computer.cores.get(core as usize).unwrap();
|
||||||
let Some(core) = computer.cores.get().unwrap().get(core) else {
|
core.send(CoreMessage::Reset).unwrap();
|
||||||
env.throw("core index was out of bounds").unwrap();
|
if wake {
|
||||||
return;
|
core.send(CoreMessage::Wake).unwrap();
|
||||||
};
|
}
|
||||||
core.wake_at(address);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_tick")]
|
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_Computer_tick")]
|
||||||
unsafe extern "C" fn tick<'local>(mut env: JNIEnv<'local>, computer: JObject<'local>) {
|
unsafe extern "C" fn tick<'local>(mut env: JNIEnv<'local>, computer: JObject<'local>) {
|
||||||
// Safety: the responsibility of asserting the pointer is valid is on the java caller
|
// Safety: the responsibility of asserting the pointer is valid is on the java caller
|
||||||
let computer = unsafe { &*Self::from_jni(&mut env, computer) };
|
let computer = unsafe { &*Self::from_jni(&mut env, computer) };
|
||||||
let cores = &computer.cores.get().unwrap();
|
for core in &computer.cores {
|
||||||
for core in cores.iter() {
|
core.send(CoreMessage::Tick).unwrap();
|
||||||
core.tick();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
302
rust/src/core.rs
302
rust/src/core.rs
|
@ -1,103 +1,122 @@
|
||||||
use std::{
|
use std::sync::{mpsc, Arc};
|
||||||
cell::{OnceCell, RefCell},
|
|
||||||
pin::Pin,
|
|
||||||
rc::Rc,
|
|
||||||
sync::{mpsc, Arc, RwLock},
|
|
||||||
};
|
|
||||||
|
|
||||||
use disarm64::decoder;
|
use disarm64::decoder;
|
||||||
use jni::{JNIEnv, JavaVM};
|
use jni::{AttachGuard, JNIEnv, JavaVM};
|
||||||
use num_derive::FromPrimitive;
|
use num_derive::FromPrimitive;
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use unicorn_engine::{Arch, ArmCpRegister, HookType, Mode, Permission, Query, RegisterARM64, Unicorn};
|
use unicorn_engine::{Arch, HookType, Mode, Unicorn};
|
||||||
use zerocopy::IntoBytes;
|
use zerocopy::IntoBytes;
|
||||||
|
|
||||||
use crate::{component::{mmio::{calculate_components_start, component_mmio_read, component_mmio_write, ComponentMmioControl}, Component, ComponentIoResult}, computer::Computer, unsync_cell::UnsyncCell};
|
use crate::{components::core::Registers, device_bus::DeviceBus, unsync_cell::UnsyncCell};
|
||||||
|
|
||||||
pub struct CoreHandle {
|
pub struct Core {
|
||||||
message_sender: mpsc::Sender<CpuMessage>,
|
registers: Arc<UnsyncCell<Registers>>,
|
||||||
|
bus: Arc<DeviceBus>,
|
||||||
|
instructions_per_tick: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
impl Core {
|
||||||
|
pub fn new(registers: Arc<UnsyncCell<Registers>>, bus: Arc<DeviceBus>, instructions_per_tick: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
registers,
|
||||||
todo REWRITEEEEEEE RAAAAH
|
bus,
|
||||||
|
instructions_per_tick,
|
||||||
|
}
|
||||||
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
pub struct CpuContext<'a> {
|
|
||||||
component_data_start: usize,
|
|
||||||
components: Arc<[RwLock<Component>]>,
|
|
||||||
/// see comment on [Computer]
|
|
||||||
component_control: Arc<UnsyncCell<ComponentMmioControl>>,
|
|
||||||
message_sender: mpsc::Sender<CpuMessage>,
|
|
||||||
component_access_futures: Vec<Pin<Box<dyn Future<Output = ComponentIoResult>>>>,
|
|
||||||
ram: Arc<UnsyncCell<[u8]>>,
|
|
||||||
stopped: bool,
|
|
||||||
_env: JNIEnv<'a>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CpuMessage {
|
pub struct CoreContext<'a> {
|
||||||
|
core: Core,
|
||||||
|
env: AttachGuard<'a>,
|
||||||
|
is_sleeping: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CoreContext<'a> {
|
||||||
|
pub fn get_env(&mut self) -> &mut JNIEnv<'a> {
|
||||||
|
&mut self.env
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CoreMessage {
|
||||||
Tick,
|
Tick,
|
||||||
Wake(Option<u64>),
|
Reset,
|
||||||
|
Wake,
|
||||||
Sleep,
|
Sleep,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoreHandle {
|
pub fn spawn_core_thread(core: Core, jvm: JavaVM, message_receiver: mpsc::Receiver<CoreMessage>) {
|
||||||
pub fn new(computer: &Computer, core_index: usize, instructions_per_tick: u32, java_vm: JavaVM) -> Self {
|
std::thread::spawn(|| core_main(core, jvm, message_receiver));
|
||||||
let (components, component_control) = (computer.components.clone(), computer.component_control.clone());
|
}
|
||||||
let (started_up_sender, started_up_receiver) = oneshot::channel();
|
|
||||||
|
|
||||||
let (message_sender, message_receiver) = mpsc::channel();
|
fn core_main(core: Core, jvm: JavaVM, message_receiver: mpsc::Receiver<CoreMessage>) {
|
||||||
let memories = (computer.shared_ram.clone(), computer.shared_rom.clone());
|
let env = jvm.attach_current_thread().unwrap();
|
||||||
|
let mut uc = Unicorn::new_with_data(
|
||||||
|
Arch::ARM64,
|
||||||
|
Mode::LITTLE_ENDIAN,
|
||||||
|
CoreContext {
|
||||||
|
core,
|
||||||
|
env,
|
||||||
|
is_sleeping: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
std::thread::spawn({
|
let bus = uc.get_data().core.bus.clone();
|
||||||
let message_sender = message_sender.clone();
|
bus.map(&mut uc);
|
||||||
|
uc.add_intr_hook(interrupt_handler).unwrap();
|
||||||
|
uc
|
||||||
|
.add_mem_hook(
|
||||||
|
HookType::MEM_INVALID,
|
||||||
|
u64::MIN,
|
||||||
|
u64::MAX,
|
||||||
|
|vcpu, mem_type, address, _, value| {
|
||||||
|
eprintln!("memory exception: {mem_type:?} at {address:X} (value: {value:X})");
|
||||||
|
|
||||||
move || {
|
vcpu.emu_stop().unwrap();
|
||||||
let env = java_vm.attach_current_thread_permanently().unwrap();
|
true
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let context = CpuContext {
|
let try_fetch_message = |stopped| {
|
||||||
component_data_start: calculate_components_start(components.len()),
|
if stopped {
|
||||||
components,
|
message_receiver.recv().map(Some).map_err(|_| ())
|
||||||
component_control,
|
} else {
|
||||||
message_sender,
|
Ok(message_receiver.try_recv().ok())
|
||||||
component_access_futures: Vec::new(),
|
}
|
||||||
ram: memories.0.clone(),
|
};
|
||||||
_env: env,
|
|
||||||
stopped: true,
|
let mut instructions_left = 0;
|
||||||
};
|
loop {
|
||||||
thread(
|
while let Some(message) = try_fetch_message(uc.get_data().is_sleeping || instructions_left == 0).transpose() {
|
||||||
core_index,
|
let Ok(message) = message else { return };
|
||||||
instructions_per_tick,
|
|
||||||
memories,
|
match message {
|
||||||
context,
|
CoreMessage::Tick => {
|
||||||
started_up_sender,
|
if uc.get_data().is_sleeping {
|
||||||
message_receiver,
|
continue;
|
||||||
)
|
}
|
||||||
|
println!("ticking");
|
||||||
|
instructions_left = uc.get_data().core.instructions_per_tick as usize;
|
||||||
|
}
|
||||||
|
CoreMessage::Reset => {
|
||||||
|
let reset_address = uc.get_data().core.registers.read().reset_address.get();
|
||||||
|
println!("reset! address: {reset_address:08X}");
|
||||||
|
uc.set_pc(reset_address).expect("error while executing cpu")
|
||||||
|
}
|
||||||
|
CoreMessage::Wake => uc.get_data_mut().is_sleeping = false,
|
||||||
|
CoreMessage::Sleep => uc.get_data_mut().is_sleeping = true,
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
println!("executing");
|
||||||
let _ = started_up_receiver.recv().expect("failed to start up core");
|
uc.emu_start(
|
||||||
|
uc.pc_read().expect("error while executing cpu"),
|
||||||
CoreHandle { message_sender }
|
0,
|
||||||
}
|
0,
|
||||||
|
instructions_left.min(1000),
|
||||||
#[unsafe(export_name = "Java_ca_sanae_golemcomputers_computer_RustNative_coreFree")]
|
)
|
||||||
unsafe extern "C" fn free<'local>(_env: JNIEnv<'local>, core: *const Self) {
|
.expect("error while executing cpu");
|
||||||
// Safety: the responsibility of verifying the pointer is valid is on the java caller
|
instructions_left = instructions_left.saturating_sub(1000);
|
||||||
drop(unsafe { Arc::from_raw(core) })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tick(&self) {
|
|
||||||
self.message_sender.send(CpuMessage::Tick).expect("cpu thread panicked :(");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wake_at(&self, start: u64) {
|
|
||||||
self.message_sender.send(CpuMessage::Wake(Some(start))).unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,110 +145,29 @@ enum Exception {
|
||||||
Unaligned = 22,
|
Unaligned = 22,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn thread(
|
fn interrupt_handler(uc: &mut Unicorn<CoreContext>, kind: u32) {
|
||||||
core_index: usize,
|
let exception = Exception::from_u32(kind).unwrap();
|
||||||
instructions_per_tick: u32,
|
|
||||||
(shared_ram, shared_rom): (Arc<UnsyncCell<[u8]>>, Arc<UnsyncCell<[u8]>>),
|
|
||||||
context: CpuContext,
|
|
||||||
started_up_sender: oneshot::Sender<()>,
|
|
||||||
message_receiver: mpsc::Receiver<CpuMessage>,
|
|
||||||
) -> Result<(), ()> {
|
|
||||||
let mut vcpu =
|
|
||||||
Unicorn::new_with_data(Arch::ARM64, Mode::LITTLE_ENDIAN, context).expect("failed to spawn unicorn engine");
|
|
||||||
|
|
||||||
const MPIDR_EL1: ArmCpRegister = ArmCpRegister::from_manual(0b11, 0b000, 0b0000, 0b0000, 0b101);
|
let pc = uc.pc_read().unwrap();
|
||||||
vcpu.reg_write_cp(CP_REG, MPIDR_EL1, core_index as u64).unwrap();
|
println!("exception: {exception:?}");
|
||||||
|
let mut opcode = 0u32;
|
||||||
vcpu
|
if let Ok(_) = uc.mem_read(pc, opcode.as_mut_bytes()) {
|
||||||
.mmio_map(
|
if let Some(opcode) = decoder::decode(opcode) {
|
||||||
0x2000_0000,
|
let mut inst = String::new();
|
||||||
0x1000,
|
disarm64::format_insn::format_insn_pc(pc, &mut inst, &opcode).unwrap();
|
||||||
Some(component_mmio_read),
|
println!("pc {pc:08X} {inst}");
|
||||||
Some(component_mmio_write),
|
|
||||||
)
|
|
||||||
.expect("failed to map mmio for core");
|
|
||||||
shared_rom
|
|
||||||
.map(&mut vcpu, 0x4000_0000, None, Permission::READ | Permission::EXEC)
|
|
||||||
.expect("failed to map memory for rom");
|
|
||||||
shared_ram.map(&mut vcpu, 0x8000_0000, None, Permission::ALL).expect("failed to map memory for rom");
|
|
||||||
use RegisterARM64::*;
|
|
||||||
|
|
||||||
vcpu
|
|
||||||
.add_intr_hook(|vcpu, excp| {
|
|
||||||
println!("interrupt: {excp}");
|
|
||||||
let exception = Exception::from_u32(excp).unwrap();
|
|
||||||
|
|
||||||
let pc = vcpu.pc_read().unwrap();
|
|
||||||
println!("exception: {exception:?}");
|
|
||||||
let mut opcode = 0u32;
|
|
||||||
if let Ok(_) = vcpu.mem_read(pc, opcode.as_mut_bytes()) {
|
|
||||||
if let Some(opcode) = decoder::decode(opcode) {
|
|
||||||
let mut inst = String::new();
|
|
||||||
disarm64::format_insn::format_insn_pc(pc, &mut inst, &opcode).unwrap();
|
|
||||||
println!("pc {pc:08X} {inst}");
|
|
||||||
} else {
|
|
||||||
println!("pc {pc:08X} - op parse failed {opcode:08X}");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!("pc {pc:08X} - can't read");
|
|
||||||
}
|
|
||||||
println!("x0 {:08X}", vcpu.reg_read(X0).unwrap());
|
|
||||||
println!("x1 {:08X}", vcpu.reg_read(X1).unwrap());
|
|
||||||
|
|
||||||
let syndrome = vcpu.query(Query::SYNDROME).unwrap();
|
|
||||||
println!("0x{syndrome:08X}");
|
|
||||||
vcpu.get_data_mut().stopped = true;
|
|
||||||
vcpu.emu_stop().unwrap();
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
vcpu
|
|
||||||
.add_mem_hook(
|
|
||||||
HookType::MEM_INVALID,
|
|
||||||
u64::MIN,
|
|
||||||
u64::MAX,
|
|
||||||
|vcpu, mem_type, address, _, value| {
|
|
||||||
eprintln!("memory exception: {mem_type:?} at {address:X} (value: {value:X})");
|
|
||||||
|
|
||||||
vcpu.emu_stop().unwrap();
|
|
||||||
true
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
started_up_sender.send(()).expect("panicked when trying to core");
|
|
||||||
let try_fetch_message = |stopped| {
|
|
||||||
if stopped {
|
|
||||||
message_receiver.recv().map(Some).map_err(|_| ())
|
|
||||||
} else {
|
} else {
|
||||||
Ok(message_receiver.try_recv().ok())
|
println!("pc {pc:08X} - op parse failed {opcode:08X}");
|
||||||
}
|
}
|
||||||
};
|
} else {
|
||||||
|
println!("pc {pc:08X} - can't read");
|
||||||
let mut instructions_left = 0;
|
|
||||||
loop {
|
|
||||||
while let Some(message) = try_fetch_message(vcpu.get_data().stopped || instructions_left == 0)? {
|
|
||||||
match message {
|
|
||||||
CpuMessage::Tick => {
|
|
||||||
if vcpu.get_data().stopped {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CpuMessage::Wake(address) => {
|
|
||||||
if let Some(address) = address {
|
|
||||||
vcpu.set_pc(address).unwrap();
|
|
||||||
println!("waking at {address}");
|
|
||||||
}
|
|
||||||
vcpu.get_data_mut().stopped = false;
|
|
||||||
|
|
||||||
let pc = vcpu.pc_read().unwrap();
|
|
||||||
println!("executing {pc:X}");
|
|
||||||
vcpu.emu_start(pc, 0, 0, instructions_per_tick as usize).expect("error while executing cpu");
|
|
||||||
}
|
|
||||||
CpuMessage::Sleep => vcpu.get_data_mut().stopped = true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vcpu.emu_start(vcpu.pc_read().unwrap(), 0, 0, instructions_left.min(1000)).expect("error while executing cpu");
|
|
||||||
instructions_left = instructions_left.saturating_sub(1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use unicorn_engine::RegisterARM64::*;
|
||||||
|
println!("X0: {:X}", uc.reg_read(X0).unwrap());
|
||||||
|
println!("X1: {:X}", uc.reg_read(X1).unwrap());
|
||||||
|
println!("X2: {:X}", uc.reg_read(X2).unwrap());
|
||||||
|
|
||||||
|
// if an exception occurs, we don't want to execute anymore
|
||||||
|
uc.get_data_mut().is_sleeping = true;
|
||||||
}
|
}
|
||||||
|
|
174
rust/src/device_bus.rs
Normal file
174
rust/src/device_bus.rs
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
use std::{num::NonZeroUsize, sync::Arc};
|
||||||
|
|
||||||
|
use bitfield_struct::bitfield;
|
||||||
|
use nodit::{interval::ie, Interval, NoditMap, OverlapError};
|
||||||
|
use unicorn_engine::{Permission, Unicorn};
|
||||||
|
use zerocopy::{little_endian::U32, FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout};
|
||||||
|
|
||||||
|
use crate::{align_up_to_page, components::AllComponent, core::CoreContext, unsync_cell::UnsyncCell};
|
||||||
|
|
||||||
|
pub const COMPONENT_TABLE_START: u64 = 0x1000_0000;
|
||||||
|
pub const COMPONENTS_START: u64 = 0x2000_0000;
|
||||||
|
pub const ROM_START: u64 = 0x4000_0000;
|
||||||
|
pub const RAM_START: u64 = 0x8000_0000;
|
||||||
|
|
||||||
|
enum Device {
|
||||||
|
Rom(Box<UnsyncCell<[u8]>>),
|
||||||
|
Ram(Box<UnsyncCell<[u8]>>),
|
||||||
|
ComponentTable,
|
||||||
|
Component(AllComponent),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u32)]
|
||||||
|
struct ComponentEntryHeader {
|
||||||
|
#[bits(31)]
|
||||||
|
_padding: u32,
|
||||||
|
filled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)]
|
||||||
|
#[repr(C)]
|
||||||
|
struct ComponentTableEntry {
|
||||||
|
header: U32,
|
||||||
|
address: U32,
|
||||||
|
name: [u8; 0x30],
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ComponentTable(Box<[u8]>);
|
||||||
|
|
||||||
|
pub struct DeviceBus {
|
||||||
|
devices: NoditMap<u64, Interval<u64>, Device>,
|
||||||
|
component_table: ComponentTable,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_non_overlapping(result: Result<(), OverlapError<Device>>) {
|
||||||
|
if result.is_err() {
|
||||||
|
panic!("insert overlapped!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceBus {
|
||||||
|
pub fn new(ram: Box<UnsyncCell<[u8]>>, rom: Box<UnsyncCell<[u8]>>, components: Vec<AllComponent>) -> Self {
|
||||||
|
let mut devices = NoditMap::new();
|
||||||
|
|
||||||
|
assert_non_overlapping(devices.insert_strict(ie(ROM_START, ROM_START + rom.len() as u64), Device::Rom(rom)));
|
||||||
|
assert_non_overlapping(devices.insert_strict(ie(RAM_START, RAM_START + ram.len() as u64), Device::Ram(ram)));
|
||||||
|
|
||||||
|
let mut component_table = {
|
||||||
|
let mut table = Box::new_uninit_slice(align_up_to_page(components.len() * size_of::<ComponentTableEntry>()));
|
||||||
|
table.zero();
|
||||||
|
unsafe { table.assume_init() }
|
||||||
|
};
|
||||||
|
assert_non_overlapping(devices.insert_strict(
|
||||||
|
ie(
|
||||||
|
COMPONENT_TABLE_START,
|
||||||
|
COMPONENT_TABLE_START + component_table.len() as u64,
|
||||||
|
),
|
||||||
|
Device::ComponentTable,
|
||||||
|
));
|
||||||
|
|
||||||
|
{
|
||||||
|
let component_table = <[ComponentTableEntry]>::mut_from_bytes_with_elems(
|
||||||
|
&mut component_table[..components.len() * size_of::<ComponentTableEntry>()],
|
||||||
|
components.len(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut component_offset = COMPONENTS_START;
|
||||||
|
for (index, component) in components.into_iter().enumerate() {
|
||||||
|
let start = component_offset;
|
||||||
|
let end = align_up_to_page(component_offset + component.memory_map_size());
|
||||||
|
println!("component offset: {start:X}..{end:X} and {}", u32::MAX);
|
||||||
|
assert!(end < ROM_START);
|
||||||
|
|
||||||
|
component_offset = end;
|
||||||
|
component_table[index] = ComponentTableEntry {
|
||||||
|
header: U32::new(ComponentEntryHeader::new().with_filled(true).into_bits()),
|
||||||
|
address: U32::new(start as u32),
|
||||||
|
name: component.name(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_non_overlapping(devices.insert_strict(ie(start, end), Device::Component(component)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
devices,
|
||||||
|
component_table: ComponentTable(component_table),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map(self: Arc<Self>, uc: &mut Unicorn<CoreContext>) {
|
||||||
|
for (interval, device) in self.devices.iter() {
|
||||||
|
let (address, size) = (interval.start(), (interval.end() - interval.start()) as usize + 1);
|
||||||
|
println!("address, size {address:X}, {size:X} {:X?}", interval);
|
||||||
|
match device {
|
||||||
|
Device::Rom(memory) => unsafe {
|
||||||
|
uc.mem_map_ptr(
|
||||||
|
address,
|
||||||
|
size,
|
||||||
|
Permission::READ | Permission::WRITE,
|
||||||
|
memory.as_mut_slice_ptr() as _,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
},
|
||||||
|
Device::Ram(memory) => unsafe {
|
||||||
|
uc.mem_map_ptr(
|
||||||
|
address,
|
||||||
|
size,
|
||||||
|
Permission::READ | Permission::WRITE | Permission::EXEC,
|
||||||
|
memory.as_mut_slice_ptr() as _,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
},
|
||||||
|
Device::ComponentTable => unsafe {
|
||||||
|
println!("component table range {address:X} + {size:X}");
|
||||||
|
uc.mem_map_ptr(address, size, Permission::READ, self.component_table.0.as_ptr() as _).unwrap()
|
||||||
|
},
|
||||||
|
Device::Component(_) => {
|
||||||
|
fn calculate_size(component: &AllComponent, offset: u64, size: usize) -> Option<NonZeroUsize> {
|
||||||
|
if offset >= component.memory_map_size() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = offset as usize;
|
||||||
|
NonZeroUsize::new(usize::min(offset + size, component.memory_map_size() as usize) - offset)
|
||||||
|
}
|
||||||
|
let bus_read = self.clone();
|
||||||
|
let bus_write = self.clone();
|
||||||
|
uc.mmio_map(
|
||||||
|
address,
|
||||||
|
size,
|
||||||
|
Some(move |uc: &mut Unicorn<CoreContext>, offset: u64, size: usize| {
|
||||||
|
let Device::Component(component) = bus_read.devices.get_at_point(address).unwrap() else {
|
||||||
|
unreachable!("device was not a component")
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(size) = calculate_size(component, offset, size).map(NonZeroUsize::get) else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut data = 0u64;
|
||||||
|
component.read(uc.get_data_mut().get_env(), offset, &mut data.as_mut_bytes()[..size]);
|
||||||
|
data
|
||||||
|
}),
|
||||||
|
Some(
|
||||||
|
move |uc: &mut Unicorn<CoreContext>, offset: u64, size: usize, data: u64| {
|
||||||
|
let Device::Component(component) = bus_write.devices.get_at_point(address).unwrap() else {
|
||||||
|
unreachable!("device was not a component")
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(size) = calculate_size(component, offset, size).map(NonZeroUsize::get) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
component.write(uc.get_data_mut().get_env(), offset, &data.to_le_bytes()[..size]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
#![deny(unsafe_op_in_unsafe_fn)]
|
#![deny(unsafe_op_in_unsafe_fn)]
|
||||||
#![feature(sync_unsafe_cell)]
|
#![feature(sync_unsafe_cell)]
|
||||||
#![feature(generic_const_exprs)]
|
|
||||||
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
|
@ -8,12 +7,13 @@ use jni::{
|
||||||
sys::{jint, JNI_VERSION_1_8},
|
sys::{jint, JNI_VERSION_1_8},
|
||||||
JNIEnv,
|
JNIEnv,
|
||||||
};
|
};
|
||||||
|
use num_traits::PrimInt;
|
||||||
|
|
||||||
pub mod component;
|
pub mod components;
|
||||||
mod computer;
|
mod computer;
|
||||||
mod core;
|
pub mod core;
|
||||||
|
mod device_bus;
|
||||||
pub mod unsync_cell;
|
pub mod unsync_cell;
|
||||||
mod memory_map;
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_positive {
|
macro_rules! assert_positive {
|
||||||
|
@ -45,6 +45,30 @@ macro_rules! assert_positive {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! component_name {
|
||||||
|
($value: expr) => {{
|
||||||
|
const DATA: [u8; 0x30] = {
|
||||||
|
let name = { $value };
|
||||||
|
if name.len() >= 0x30 {
|
||||||
|
panic!("name length must be less than 64 characters")
|
||||||
|
}
|
||||||
|
let mut new_name = [0; 0x30];
|
||||||
|
let mut index = name.len();
|
||||||
|
loop {
|
||||||
|
index -= 1;
|
||||||
|
new_name[index] = name[index];
|
||||||
|
if index == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_name
|
||||||
|
};
|
||||||
|
|
||||||
|
DATA.clone()
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! range_of_field {
|
macro_rules! range_of_field {
|
||||||
($type: ty, $field: ident) => {{
|
($type: ty, $field: ident) => {{
|
||||||
|
@ -68,19 +92,19 @@ fn overlapping<Idx: PartialOrd>(a: &Range<Idx>, b: &Range<Idx>) -> bool {
|
||||||
a.contains(&b.start) && a.contains(&b.end)
|
a.contains(&b.start) && a.contains(&b.end)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subtract_range(a: &Range<usize>, b: &Range<usize>) -> Range<usize> {
|
// fn subtract_range(a: &Range<usize>, b: &Range<usize>) -> Range<usize> {
|
||||||
a.start - b.start..a.end - b.end
|
// a.start - b.start..a.end - b.end
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn align_up_to_page<T: PrimInt>(value: T) -> T {
|
||||||
|
align_up(value, T::one().unsigned_shl(12))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn align_up(value: u64, alignment: u64) -> u64 {
|
pub fn align_up<T: PrimInt>(value: T, alignment: T) -> T {
|
||||||
(value + alignment - 1) & !(alignment - 1)
|
(value + alignment - T::one()) & !(alignment - T::one())
|
||||||
}
|
|
||||||
|
|
||||||
struct UnicornWrap {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "system" fn JNI_OnLoad<'local>(_: JNIEnv<'local>, _: usize) -> jint {
|
extern "system" fn JNI_OnLoad<'local>(_: JNIEnv<'local>, _: usize) -> jint {
|
||||||
JNI_VERSION_1_8
|
JNI_VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
pub const COMPONENTS_START: u64 = 0x2000_0000;
|
|
||||||
pub const ROM_START: u64 = 0x4000_0000;
|
|
||||||
pub const RAM_START: u64 = 0x8000_0000;
|
|
|
@ -2,7 +2,7 @@ use std::cell::SyncUnsafeCell;
|
||||||
|
|
||||||
use jni::{objects::JByteBuffer, JNIEnv};
|
use jni::{objects::JByteBuffer, JNIEnv};
|
||||||
use unicorn_engine::{uc_error, Permission, Unicorn};
|
use unicorn_engine::{uc_error, Permission, Unicorn};
|
||||||
use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout};
|
use zerocopy::{FromBytes, FromZeros, IntoBytes, KnownLayout};
|
||||||
|
|
||||||
/// A simple replacement for [std::cell::Cell] in Sync contexts which explicitly does not try to synchronize the contents.
|
/// A simple replacement for [std::cell::Cell] in Sync contexts which explicitly does not try to synchronize the contents.
|
||||||
/// This is used for contexts where unsynchronized access is explicitly fine.
|
/// This is used for contexts where unsynchronized access is explicitly fine.
|
||||||
|
@ -19,6 +19,12 @@ impl<T> UnsyncCell<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> UnsyncCell<[T]> {
|
||||||
|
pub fn as_mut_slice_ptr(&self) -> *mut [T] {
|
||||||
|
self.0.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Sized + Copy> UnsyncCell<T> {
|
impl<T: Sized + Copy> UnsyncCell<T> {
|
||||||
pub fn new(value: T) -> Self {
|
pub fn new(value: T) -> Self {
|
||||||
Self(SyncUnsafeCell::new(value))
|
Self(SyncUnsafeCell::new(value))
|
||||||
|
@ -35,24 +41,36 @@ impl<T: Sized + Copy> UnsyncCell<T> {
|
||||||
|
|
||||||
impl<T: Sized + Copy + IntoBytes> UnsyncCell<T> {
|
impl<T: Sized + Copy + IntoBytes> UnsyncCell<T> {
|
||||||
// N must be the same as the size of U
|
// N must be the same as the size of U
|
||||||
pub fn read_into<U: FromBytes>(&self, offset: usize) -> U
|
// pub fn read_into<U: FromBytes>(&self, offset: usize) -> U
|
||||||
where
|
// where
|
||||||
[u8; size_of::<U>()]:,
|
// [u8; size_of::<U>()]:,
|
||||||
{
|
// {
|
||||||
let mut buffer = [0; size_of::<U>()];
|
// let mut buffer = [0; size_of::<U>()];
|
||||||
let end = (offset + size_of::<U>()).min(size_of::<T>());
|
// let end = (offset + size_of::<U>()).min(size_of::<T>());
|
||||||
assert!(end - offset <= size_of::<U>());
|
// assert!(end - offset <= size_of::<U>());
|
||||||
// Safety: both pointers are valid for 0..(end - offset)
|
// // Safety: both pointers are valid for 0..(end - offset)
|
||||||
unsafe { (self.as_mut_ptr() as *const u8).copy_to(buffer.as_mut_ptr(), end - offset) };
|
// unsafe { (self.as_mut_ptr() as *const u8).copy_to(buffer.as_mut_ptr(), end - offset) };
|
||||||
U::read_from_bytes(&buffer).expect("N was not the same as the size of U")
|
// U::read_from_bytes(&buffer).expect("N was not the same as the size of U")
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn read_into_byte_slice(&self, offset: usize, data: &mut [u8]) {
|
||||||
|
let end = (offset + data.len()).min(size_of::<T>());
|
||||||
|
assert!(end - offset <= data.len());
|
||||||
|
unsafe { (self.as_mut_ptr() as *const u8).copy_to(data.as_mut_ptr(), end - offset) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Sized + Copy + FromBytes> UnsyncCell<T> {
|
impl<T: Sized + Copy + FromBytes> UnsyncCell<T> {
|
||||||
pub fn write_from<U: IntoBytes + Immutable>(&self, offset: usize, value: &U) {
|
// pub fn write_from<U: IntoBytes + Immutable>(&self, offset: usize, value: &U) {
|
||||||
let end = (offset + size_of::<U>()).min(size_of::<T>());
|
// let end = (offset + size_of::<U>()).min(size_of::<T>());
|
||||||
assert!(end - offset <= size_of::<U>());
|
// assert!(end - offset <= size_of::<U>());
|
||||||
unsafe { (self.as_mut_ptr() as *mut u8).copy_from(value.as_bytes().as_ptr(), end - offset) };
|
// unsafe { (self.as_mut_ptr() as *mut u8).copy_from(value.as_bytes().as_ptr(), end - offset) };
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn write_from_byte_slice(&self, offset: usize, data: &[u8]) {
|
||||||
|
let end = (offset + data.len()).min(size_of::<T>());
|
||||||
|
assert!(end - offset <= data.len());
|
||||||
|
unsafe { (self.as_mut_ptr() as *mut u8).copy_from(data.as_ptr(), end - offset) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +87,7 @@ impl<T: Sized + Copy> UnsyncCell<[T]> {
|
||||||
memory.zero();
|
memory.zero();
|
||||||
|
|
||||||
// Safety: UnsafeCell transparently wraps the slice, meaning the two types are semantically identical
|
// Safety: UnsafeCell transparently wraps the slice, meaning the two types are semantically identical
|
||||||
unsafe { std::mem::transmute::<Box<[T]>, Box<Self>>(memory.assume_init()) }
|
unsafe { std::mem::transmute::<Box<[T]>, Box<UnsyncCell<[T]>>>(memory.assume_init()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_mut_ptr(&self) -> *mut [T] {
|
fn as_mut_ptr(&self) -> *mut [T] {
|
||||||
|
@ -80,6 +98,23 @@ impl<T: Sized + Copy> UnsyncCell<[T]> {
|
||||||
self.as_mut_ptr().len()
|
self.as_mut_ptr().len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn map<D>(
|
||||||
|
&self,
|
||||||
|
vcpu: &mut Unicorn<D>,
|
||||||
|
address: u64,
|
||||||
|
size: Option<usize>,
|
||||||
|
permissions: Permission,
|
||||||
|
) -> Result<(), uc_error> {
|
||||||
|
let size = size.unwrap_or(self.len());
|
||||||
|
if size > self.len() {
|
||||||
|
return Err(uc_error::ARG);
|
||||||
|
}
|
||||||
|
println!("mapping {permissions:?} at {address:08X} for {size:08X}");
|
||||||
|
unsafe { vcpu.mem_map_ptr(address, size, permissions, self.as_mut_ptr() as *mut _) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnsyncCell<[u8]> {
|
||||||
pub fn update_from_slice(&self, bytes: &[u8]) -> Result<(), usize> {
|
pub fn update_from_slice(&self, bytes: &[u8]) -> Result<(), usize> {
|
||||||
if self.len() < bytes.len() {
|
if self.len() < bytes.len() {
|
||||||
return Err(bytes.len() - self.len());
|
return Err(bytes.len() - self.len());
|
||||||
|
@ -107,21 +142,6 @@ impl<T: Sized + Copy> UnsyncCell<[T]> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map<D>(
|
|
||||||
&self,
|
|
||||||
vcpu: &mut Unicorn<D>,
|
|
||||||
address: u64,
|
|
||||||
size: Option<usize>,
|
|
||||||
permissions: Permission,
|
|
||||||
) -> Result<(), uc_error> {
|
|
||||||
let size = size.unwrap_or(self.len());
|
|
||||||
if size > self.len() {
|
|
||||||
return Err(uc_error::ARG);
|
|
||||||
}
|
|
||||||
println!("mapping {permissions:?} at {address:08X} for {size:08X}");
|
|
||||||
unsafe { vcpu.mem_map_ptr(address, size, permissions, self.as_mut_ptr() as *mut _) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone + Copy> Clone for UnsyncCell<T> {
|
impl<T: Clone + Copy> Clone for UnsyncCell<T> {
|
||||||
|
|
|
@ -3,6 +3,7 @@ pluginManagement {
|
||||||
maven("https://maven.fabricmc.net/") {
|
maven("https://maven.fabricmc.net/") {
|
||||||
name = "Fabric"
|
name = "Fabric"
|
||||||
}
|
}
|
||||||
|
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,12 @@ import ca.sanae.golemcomputers.GolemComputers
|
||||||
import com.mojang.serialization.MapCodec
|
import com.mojang.serialization.MapCodec
|
||||||
import net.minecraft.block.BlockState
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.block.BlockWithEntity
|
import net.minecraft.block.BlockWithEntity
|
||||||
|
import net.minecraft.block.CommandBlock
|
||||||
import net.minecraft.block.entity.BlockEntity
|
import net.minecraft.block.entity.BlockEntity
|
||||||
import net.minecraft.block.entity.BlockEntityTicker
|
import net.minecraft.block.entity.BlockEntityTicker
|
||||||
import net.minecraft.block.entity.BlockEntityType
|
import net.minecraft.block.entity.BlockEntityType
|
||||||
import net.minecraft.entity.player.PlayerEntity
|
import net.minecraft.entity.player.PlayerEntity
|
||||||
|
import net.minecraft.server.command.SayCommand
|
||||||
import net.minecraft.sound.BlockSoundGroup
|
import net.minecraft.sound.BlockSoundGroup
|
||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
import net.minecraft.util.ActionResult
|
import net.minecraft.util.ActionResult
|
||||||
|
@ -51,8 +53,9 @@ class GolemBlock :
|
||||||
if (world.isClient()) {
|
if (world.isClient()) {
|
||||||
return ActionResult.SUCCESS
|
return ActionResult.SUCCESS
|
||||||
}
|
}
|
||||||
|
world.server!!.playerManager.broadcast(Text.literal("Hello world!"), false)
|
||||||
blockEntity.incrementClicks();
|
blockEntity.incrementClicks();
|
||||||
player.sendMessage(Text.literal("You've clicked the block for the ${blockEntity.getClicks()}th time."), true);
|
// player.sendMessage(Text.literal("You've clicked the block for the ${blockEntity.getClicks()}th time."), true);
|
||||||
|
|
||||||
return ActionResult.SUCCESS_SERVER
|
return ActionResult.SUCCESS_SERVER
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ca.sanae.golemcomputers.blocks
|
||||||
|
|
||||||
import ca.sanae.golemcomputers.GolemComputers
|
import ca.sanae.golemcomputers.GolemComputers
|
||||||
import ca.sanae.golemcomputers.computer.Computer
|
import ca.sanae.golemcomputers.computer.Computer
|
||||||
|
import ca.sanae.golemcomputers.computer.components.ChatComponent
|
||||||
import net.fabricmc.api.EnvType
|
import net.fabricmc.api.EnvType
|
||||||
import net.fabricmc.api.Environment
|
import net.fabricmc.api.Environment
|
||||||
import net.minecraft.block.BlockState
|
import net.minecraft.block.BlockState
|
||||||
|
@ -10,6 +11,8 @@ import net.minecraft.nbt.NbtCompound
|
||||||
import net.minecraft.registry.RegistryWrapper
|
import net.minecraft.registry.RegistryWrapper
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.world.World
|
import net.minecraft.world.World
|
||||||
|
import java.nio.channels.FileChannel
|
||||||
|
import kotlin.io.path.Path
|
||||||
|
|
||||||
class GolemBlockEntity(pos: BlockPos?, state: BlockState?) :
|
class GolemBlockEntity(pos: BlockPos?, state: BlockState?) :
|
||||||
BlockEntity(GolemComputers.GOLEM_BLOCK_ENTITY, pos, state) {
|
BlockEntity(GolemComputers.GOLEM_BLOCK_ENTITY, pos, state) {
|
||||||
|
@ -34,7 +37,17 @@ class GolemBlockEntity(pos: BlockPos?, state: BlockState?) :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setWorld(world: World?) {
|
override fun setWorld(world: World?) {
|
||||||
computer = if (!world!!.isClient) Computer(world) else null
|
val path = Path("/home/aubrey/Projects/golem-software/assembly.bin")
|
||||||
|
val channel = FileChannel.open(path);
|
||||||
|
val buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size())
|
||||||
|
if (!world!!.isClient) {
|
||||||
|
val computer = Computer.Builder(world, buffer, 1u)
|
||||||
|
.addChat(ChatComponent(world.server!!))
|
||||||
|
.addCore(1000)
|
||||||
|
.build()
|
||||||
|
computer.resetCore(0, true);
|
||||||
|
this.computer = computer;
|
||||||
|
}
|
||||||
super.setWorld(world)
|
super.setWorld(world)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,62 +1,66 @@
|
||||||
package ca.sanae.golemcomputers.computer
|
package ca.sanae.golemcomputers.computer
|
||||||
|
|
||||||
import ca.sanae.golemcomputers.computer.RustNative.Companion.cleaner
|
import ca.sanae.golemcomputers.computer.NativeProtobuf.ChatComponentInit
|
||||||
|
import ca.sanae.golemcomputers.computer.NativeProtobuf.ComponentInit
|
||||||
|
import ca.sanae.golemcomputers.computer.NativeProtobuf.ComputerInit
|
||||||
|
import ca.sanae.golemcomputers.computer.NativeProtobuf.CoreComponentInit
|
||||||
|
import ca.sanae.golemcomputers.computer.RustNative.cleaner
|
||||||
import ca.sanae.golemcomputers.computer.components.ChatComponent
|
import ca.sanae.golemcomputers.computer.components.ChatComponent
|
||||||
import ca.sanae.golemcomputers.computer.components.Component
|
|
||||||
import net.minecraft.world.World
|
import net.minecraft.world.World
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.channels.FileChannel
|
import java.nio.channels.FileChannel
|
||||||
import java.nio.file.Files
|
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
|
|
||||||
class Computer(val world: World) {
|
class Computer private constructor(val world: World, init: ComputerInit, initObjects: Array<Any>) {
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
private val address: Long = 0
|
private val address: Long = 0
|
||||||
|
|
||||||
init {
|
init {
|
||||||
new(1, 0x1000, intArrayOf(0x10000))
|
new(init.toByteArray(), initObjects)
|
||||||
cleaner.register(this) {
|
cleaner.register(this) {
|
||||||
free()
|
free()
|
||||||
}
|
}
|
||||||
val path = Path("/home/aubrey/Projects/golem-software/assembly.bin")
|
|
||||||
val channel = FileChannel.open(path);
|
|
||||||
val buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size())
|
|
||||||
val wakeAddress = buffer.getUnsignedInt();
|
|
||||||
|
|
||||||
updateRomTest(buffer.rewind())
|
|
||||||
println("waking at 0x${wakeAddress.toString(16)}")
|
|
||||||
insertComponent(0, ChatComponent(this))
|
|
||||||
wakeCoreAt(0, wakeAddress)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ByteBuffer.getUnsignedInt(): Long {
|
private external fun new(toByteArray: ByteArray, initObjects: Array<Any>): Long
|
||||||
return Integer.reverseBytes(this.getInt()).toLong() and 0xFFFFFFFFL
|
|
||||||
}
|
|
||||||
|
|
||||||
fun insertComponent(coreIndex: Int, component: Component) {
|
|
||||||
insertComponentNative(
|
|
||||||
coreIndex,
|
|
||||||
component,
|
|
||||||
component.info.typeName.toString(),
|
|
||||||
component.info.protocol.toString(),
|
|
||||||
component.info.bytesPerTick,
|
|
||||||
component.info.version
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private external fun new(componentCapacity: Int, memorySize: Int, coreIps: IntArray): Long
|
|
||||||
private external fun free()
|
private external fun free()
|
||||||
private external fun updateRomTest(data: ByteBuffer)
|
external fun resetCore(core: Int, wakeCore: Boolean)
|
||||||
private external fun wakeCoreAt(coreIndex: Int, address: Long)
|
|
||||||
private external fun insertComponentNative(
|
|
||||||
index: Int,
|
|
||||||
component: Component,
|
|
||||||
typeName: String,
|
|
||||||
protocol: String,
|
|
||||||
bytesPerTick: UInt,
|
|
||||||
version: UInt
|
|
||||||
)
|
|
||||||
|
|
||||||
external fun tick();
|
external fun tick();
|
||||||
|
|
||||||
|
class Builder(val world: World, rom: ByteBuffer, ramPages: UInt) {
|
||||||
|
private val objects: ArrayList<Any> = ArrayList()
|
||||||
|
private val builder: ComputerInit.Builder = ComputerInit.newBuilder()
|
||||||
|
|
||||||
|
init {
|
||||||
|
builder.setPrimaryRomIndex(addObject(rom))
|
||||||
|
builder.setPrimaryRamPages(ramPages.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addObject(value: Any): Int {
|
||||||
|
val index = objects.size
|
||||||
|
objects.add(value)
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addCore(instructionsPerTick: Int): Builder {
|
||||||
|
builder.addComponents(
|
||||||
|
ComponentInit.newBuilder()
|
||||||
|
.setCore(CoreComponentInit.newBuilder().setInstructionsPerTick(instructionsPerTick).build())
|
||||||
|
)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addChat(chat: ChatComponent): Builder {
|
||||||
|
builder.addComponents(
|
||||||
|
ComponentInit.newBuilder().setChat(ChatComponentInit.newBuilder().setJavaIndex(addObject(chat)).build())
|
||||||
|
)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): Computer {
|
||||||
|
return Computer(world, builder.build(), objects.toArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -6,27 +6,24 @@ import java.lang.ref.Cleaner
|
||||||
import java.nio.channels.Channels
|
import java.nio.channels.Channels
|
||||||
|
|
||||||
|
|
||||||
class RustNative private constructor() {
|
object RustNative {
|
||||||
companion object {
|
val cleaner: Cleaner = Cleaner.create()
|
||||||
val cleaner: Cleaner = Cleaner.create()
|
|
||||||
private val instance: RustNative = RustNative();
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val nativeLibraryName = System.mapLibraryName("golem_computers")
|
val nativeLibraryName = System.mapLibraryName("golem_computers")
|
||||||
val tempFile: File = File.createTempFile("extracted_", nativeLibraryName)
|
val tempFile: File = File.createTempFile("extracted_", nativeLibraryName)
|
||||||
|
|
||||||
val classLoader = RustNative::class.java.getClassLoader()
|
val classLoader = RustNative::class.java.getClassLoader()
|
||||||
|
|
||||||
Channels.newChannel(classLoader.getResourceAsStream(nativeLibraryName)!!).use { src ->
|
Channels.newChannel(classLoader.getResourceAsStream(nativeLibraryName)!!).use { src ->
|
||||||
FileOutputStream(tempFile).channel.use { dst ->
|
FileOutputStream(tempFile).channel.use { dst ->
|
||||||
dst.transferFrom(src, 0, Long.MAX_VALUE)
|
dst.transferFrom(src, 0, Long.MAX_VALUE)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
System.load(tempFile.absolutePath)
|
|
||||||
}
|
}
|
||||||
|
System.load(tempFile.absolutePath)
|
||||||
|
}
|
||||||
|
|
||||||
fun isInitialized(): Boolean {
|
fun isInitialized(): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,29 +1,13 @@
|
||||||
package ca.sanae.golemcomputers.computer.components
|
package ca.sanae.golemcomputers.computer.components
|
||||||
|
|
||||||
import ca.sanae.golemcomputers.GolemComputers
|
import net.minecraft.server.MinecraftServer
|
||||||
import ca.sanae.golemcomputers.computer.Computer
|
|
||||||
import kotlinx.io.bytestring.decodeToString
|
|
||||||
import kotlinx.io.bytestring.getByteString
|
|
||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
class ChatComponent(private val computer: Computer) :
|
class ChatComponent(val server: MinecraftServer) {
|
||||||
Component(GolemComputers.id("chat"), GolemComputers.id("string_stream"), 10000, 0u) {
|
@Suppress("unused")
|
||||||
var chatBuffer: String = ""
|
fun sendChatMessage(string: String) {
|
||||||
override fun gotByteChunk(buffer: ByteBuffer) {
|
server.execute {
|
||||||
chatBuffer += buffer.getByteString().decodeToString()
|
server.playerManager.broadcast(Text.of(string), true)
|
||||||
while (true) {
|
|
||||||
val index = chatBuffer.indexOf('\u0000')
|
|
||||||
if (index == -1) break
|
|
||||||
val (message, new) = chatBuffer.splitAtIndex(index)
|
|
||||||
chatBuffer = new
|
|
||||||
computer.world.server!!.sendMessage(Text.of(message))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun restarted() {
|
|
||||||
chatBuffer = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String.splitAtIndex(index: Int) = take(index) to substring(index)
|
|
||||||
}
|
}
|
|
@ -1,31 +0,0 @@
|
||||||
package ca.sanae.golemcomputers.computer.components
|
|
||||||
|
|
||||||
import com.github.pbbl.heap.ByteBufferPool
|
|
||||||
import com.jogamp.common.util.LFRingbuffer
|
|
||||||
import com.jogamp.common.util.Ringbuffer
|
|
||||||
import net.minecraft.util.Identifier
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
abstract class Component(val info: ComponentInfo) {
|
|
||||||
@Suppress("unused")
|
|
||||||
private val address: Long = 0
|
|
||||||
private val pool = ByteBufferPool()
|
|
||||||
abstract fun gotByteChunk(buffer: ByteBuffer)
|
|
||||||
private external fun writeBytesNative(buffer: ByteBuffer): Boolean
|
|
||||||
fun writeBytes(lambda: ((size: Int) -> ByteBuffer) -> ByteBuffer) {
|
|
||||||
synchronized(pool) {
|
|
||||||
val buffer = lambda(pool::take)
|
|
||||||
val overflowed = writeBytesNative(buffer)
|
|
||||||
pool.give(buffer)
|
|
||||||
if (overflowed) {
|
|
||||||
overflowShutdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// called when the component runs out of runway to store unread data
|
|
||||||
open fun overflowShutdown() {}
|
|
||||||
abstract fun restarted()
|
|
||||||
|
|
||||||
data class ComponentInfo(val typeName: Identifier, val protocol: Identifier, val bytesPerTick: UInt, val version: UInt)
|
|
||||||
}
|
|
Loading…
Reference in a new issue